PM-3349 PM-3350 Improved code safety with try...catch, better invoke on main thread and better null handling.

This commit is contained in:
Federico Maccaroni 2024-01-19 15:01:31 -03:00
parent 01ee1ff845
commit 4717f5e230
No known key found for this signature in database
GPG key ID: 5D233F8F2B034536
21 changed files with 612 additions and 377 deletions

View file

@ -1,5 +1,6 @@
using AndroidX.AppCompat.View.Menu; using AndroidX.AppCompat.View.Menu;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Google.Android.Material.BottomNavigation; using Google.Android.Material.BottomNavigation;
using Microsoft.Maui.Handlers; using Microsoft.Maui.Handlers;
@ -90,7 +91,17 @@ namespace Bit.App.Handlers
if(e.Item is MenuItemImpl item) if(e.Item is MenuItemImpl item)
{ {
System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot."); System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot.");
MainThread.BeginInvokeOnMainThread(async () => await _tabbedPage.CurrentPage.Navigation.PopToRootAsync()); MainThread.BeginInvokeOnMainThread(async () =>
{
try
{
await _tabbedPage.CurrentPage.Navigation.PopToRootAsync();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
} }
} }

View file

@ -15,6 +15,7 @@ using CoreNFC;
using Foundation; using Foundation;
using Microsoft.Maui.Platform; using Microsoft.Maui.Platform;
using UIKit; using UIKit;
using UserNotifications;
using WatchConnectivity; using WatchConnectivity;
namespace Bit.iOS namespace Bit.iOS
@ -41,73 +42,78 @@ namespace Bit.iOS
private IStateService _stateService; private IStateService _stateService;
private IEventService _eventService; private IEventService _eventService;
private LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>(); private readonly LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
public override bool FinishedLaunching(UIApplication app, NSDictionary options) public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{ {
InitApp(); try
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
ConnectToWatchIfNeededAsync().FireAndForget();
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
{ {
try InitApp();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
ConnectToWatchIfNeededAsync().FireAndForget();
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
{ {
if (message.Command == "startEventTimer") try
{ {
StartEventTimer(); if (message.Command == "startEventTimer")
}
else if (message.Command == "stopEventTimer")
{
var task = StopEventTimerAsync();
}
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
MainThread.BeginInvokeOnMainThread(() =>
{ {
iOSCoreHelpers.AppearanceAdjustments(); StartEventTimer();
});
}
else if (message.Command == "listenYubiKeyOTP")
{
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
}
else if (message.Command == "unlocked")
{
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault())
{
await ASHelpers.ReplaceAllIdentities();
} }
} else if (message.Command == "stopEventTimer")
else if (message.Command == "showAppExtension")
{
MainThread.BeginInvokeOnMainThread(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
}
else if (message.Command == "syncCompleted")
{
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
{ {
var success = data["successfully"] as bool?; var task = StopEventTimerAsync();
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12) }
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
iOSCoreHelpers.AppearanceAdjustments();
});
}
else if (message.Command == "listenYubiKeyOTP" && message.Data is bool listen)
{
iOSCoreHelpers.ListenYubiKey(listen, _deviceActionService, _nfcSession, _nfcDelegate);
}
else if (message.Command == "unlocked")
{
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault())
{ {
await ASHelpers.ReplaceAllIdentities(); await ASHelpers.ReplaceAllIdentities();
} }
} }
} else if (message.Command == "showAppExtension")
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
message.Command == "restoredCipher")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{ {
await MainThread.InvokeOnMainThreadAsync(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
}
else if (message.Command == "syncCompleted")
{
if (message.Data is Dictionary<string, object> data && data.TryGetValue("successfully", out var value))
{
var success = value as bool?;
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
{
await ASHelpers.ReplaceAllIdentities();
}
}
}
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
message.Command == "restoredCipher")
{
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
return;
}
if (await ASHelpers.IdentitiesCanIncremental()) if (await ASHelpers.IdentitiesCanIncremental())
{ {
var cipherId = message.Data as string; var cipherId = message.Data as string;
@ -125,11 +131,13 @@ namespace Bit.iOS
} }
await ASHelpers.ReplaceAllIdentities(); await ASHelpers.ReplaceAllIdentities();
} }
} else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{ {
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
return;
}
if (await ASHelpers.IdentitiesCanIncremental()) if (await ASHelpers.IdentitiesCanIncremental())
{ {
var identity = ASHelpers.ToCredentialIdentity( var identity = ASHelpers.ToCredentialIdentity(
@ -144,101 +152,145 @@ namespace Bit.iOS
} }
await ASHelpers.ReplaceAllIdentities(); await ASHelpers.ReplaceAllIdentities();
} }
} else if (message.Command == "logout" && UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
else if (message.Command == "logout")
{
if (_deviceActionService.SystemMajorVersion() >= 12)
{ {
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
} }
} else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher") && UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
&& _deviceActionService.SystemMajorVersion() >= 12)
{
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
{
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (timeoutAction == VaultTimeoutAction.Logout)
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
else
{ {
await ASHelpers.ReplaceAllIdentities(); await ASHelpers.ReplaceAllIdentities();
} }
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
{
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
if (timeoutAction == VaultTimeoutAction.Logout)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
}
else
{
await ASHelpers.ReplaceAllIdentities();
}
}
} }
} catch (Exception ex)
catch (Exception ex) {
{ LoggerHelper.LogEvenIfCantBeResolved(ex);
LoggerHelper.LogEvenIfCantBeResolved(ex); }
} });
});
var finishedLaunching = base.FinishedLaunching(app, options); var finishedLaunching = base.FinishedLaunching(app, options);
ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources); ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources);
iOSCoreHelpers.AppearanceAdjustments(); iOSCoreHelpers.AppearanceAdjustments();
return finishedLaunching; return finishedLaunching;
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
} }
public override void OnResignActivation(UIApplication uiApplication) public override void OnResignActivation(UIApplication uiApplication)
{ {
if (UIApplication.SharedApplication.KeyWindow != null) try
{ {
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame) if (UIApplication.SharedApplication.KeyWindow != null)
{ {
Tag = SPLASH_VIEW_TAG var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
}; {
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame) Tag = SPLASH_VIEW_TAG
{ };
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToPlatform() var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
}; {
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png"); BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToPlatform()
var frame = new CGRect(0, 0, 280, 100); //Setting image width to avoid it being larger and getting cropped on smaller devices. This harcoded size should be good even for very small devices. };
var imageView = new UIImageView(frame) var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
{ var frame = new CGRect(0, 0, 280, 100); //Setting image width to avoid it being larger and getting cropped on smaller devices. This harcoded size should be good even for very small devices.
Image = logo, var imageView = new UIImageView(frame)
Center = new CGPoint(view.Center.X, view.Center.Y - 30), {
ContentMode = UIViewContentMode.ScaleAspectFit Image = logo,
}; Center = new CGPoint(view.Center.X, view.Center.Y - 30),
view.AddSubview(backgroundView); ContentMode = UIViewContentMode.ScaleAspectFit
view.AddSubview(imageView); };
UIApplication.SharedApplication.KeyWindow.AddSubview(view); view.AddSubview(backgroundView);
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view); view.AddSubview(imageView);
UIApplication.SharedApplication.KeyWindow.EndEditing(true); UIApplication.SharedApplication.KeyWindow.AddSubview(view);
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
}
base.OnResignActivation(uiApplication);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
} }
base.OnResignActivation(uiApplication);
} }
public override void DidEnterBackground(UIApplication uiApplication) public override void DidEnterBackground(UIApplication uiApplication)
{ {
if (_stateService != null && _deviceActionService != null) try
{ {
_stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime()); if (_stateService != null && _deviceActionService != null)
} {
_stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
_messagingService?.Send("slept"); _messagingService?.Send("slept");
base.DidEnterBackground(uiApplication); base.DidEnterBackground(uiApplication);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
} }
public override void OnActivated(UIApplication uiApplication) public override async void OnActivated(UIApplication uiApplication)
{ {
base.OnActivated(uiApplication); try
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0; {
UIApplication.SharedApplication.KeyWindow? base.OnActivated(uiApplication);
.ViewWithTag(SPLASH_VIEW_TAG)?
.RemoveFromSuperview();
ThemeManager.UpdateThemeOnPagesAsync(); if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
{
await UNUserNotificationCenter.Current.SetBadgeCountAsync(0);
}
else
{
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
}
UIApplication.SharedApplication.KeyWindow?
.ViewWithTag(SPLASH_VIEW_TAG)?
.RemoveFromSuperview();
ThemeManager.UpdateThemeOnPagesAsync();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
public override void WillEnterForeground(UIApplication uiApplication) public override void WillEnterForeground(UIApplication uiApplication)
{ {
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND); try
base.WillEnterForeground(uiApplication); {
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
base.WillEnterForeground(uiApplication);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
[Export("application:openURL:sourceApplication:annotation:")] [Export("application:openURL:sourceApplication:annotation:")]
@ -249,15 +301,30 @@ namespace Bit.iOS
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{ {
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options); try
{
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return false;
}
} }
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity, public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
UIApplicationRestorationHandler completionHandler) UIApplicationRestorationHandler completionHandler)
{ {
if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler)) try
{ {
return true; if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler))
{
return true;
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
} }
return base.ContinueUserActivity(application, userActivity, completionHandler); return base.ContinueUserActivity(application, userActivity, completionHandler);
} }
@ -265,33 +332,68 @@ namespace Bit.iOS
[Export("application:didFailToRegisterForRemoteNotificationsWithError:")] [Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error) public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{ {
_pushHandler?.OnErrorReceived(error); try
{
_pushHandler?.OnErrorReceived(error);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")] [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{ {
_pushHandler?.OnRegisteredSuccess(deviceToken); try
{
_pushHandler?.OnRegisteredSuccess(deviceToken);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
[Export("application:didRegisterUserNotificationSettings:")] [Export("application:didRegisterUserNotificationSettings:")]
public void DidRegisterUserNotificationSettings(UIApplication application, public void DidRegisterUserNotificationSettings(UIApplication application,
UIUserNotificationSettings notificationSettings) UIUserNotificationSettings notificationSettings)
{ {
application.RegisterForRemoteNotifications(); try
{
application.RegisterForRemoteNotifications();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
[Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")] [Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler) Action<UIBackgroundFetchResult> completionHandler)
{ {
_pushHandler?.OnMessageReceived(userInfo); try
{
_pushHandler?.OnMessageReceived(userInfo);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
[Export("application:didReceiveRemoteNotification:")] [Export("application:didReceiveRemoteNotification:")]
public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo) public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{ {
_pushHandler?.OnMessageReceived(userInfo); try
{
_pushHandler?.OnMessageReceived(userInfo);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
public void InitApp() public void InitApp()
@ -304,17 +406,6 @@ namespace Bit.iOS
// Migration services // Migration services
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService()); ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
// 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
// });
//});
iOSCoreHelpers.RegisterLocalServices(); iOSCoreHelpers.RegisterLocalServices();
RegisterPush(); RegisterPush();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
@ -328,7 +419,7 @@ namespace Bit.iOS
_nfcDelegate = new Core.NFCReaderDelegate((success, message) => _nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
_messagingService.Send("gotYubiKeyOTP", message)); _messagingService.Send("gotYubiKeyOTP", message));
iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync()); iOSCoreHelpers.Bootstrap(ApplyManagedSettingsAsync);
} }
private void RegisterPush() private void RegisterPush()
@ -373,31 +464,45 @@ namespace Bit.iOS
_eventTimer = null; _eventTimer = null;
MainThread.BeginInvokeOnMainThread(() => MainThread.BeginInvokeOnMainThread(() =>
{ {
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer => try
{ {
var task = Task.Run(() => _eventService.UploadEventsAsync()); _eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
}); {
_eventService?.UploadEventsAsync().FireAndForget();
});
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}); });
} }
private async Task StopEventTimerAsync() private async Task StopEventTimerAsync()
{ {
_eventTimer?.Invalidate(); try
_eventTimer?.Dispose();
_eventTimer = null;
if (_eventBackgroundTaskId > 0)
{ {
_eventTimer?.Invalidate();
_eventTimer?.Dispose();
_eventTimer = null;
if (_eventBackgroundTaskId > 0)
{
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
}
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
{
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
});
await _eventService.UploadEventsAsync();
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId); UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0; _eventBackgroundTaskId = 0;
} }
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() => catch (Exception ex)
{ {
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId); LoggerHelper.LogEvenIfCantBeResolved(ex);
_eventBackgroundTaskId = 0; }
});
await _eventService.UploadEventsAsync();
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
_eventBackgroundTaskId = 0;
} }
private async Task ApplyManagedSettingsAsync() private async Task ApplyManagedSettingsAsync()

View file

@ -81,7 +81,7 @@ namespace Bit.App
{ {
get get
{ {
return Application.Current.Windows.OfType<ResumeWindow>().FirstOrDefault(w => w.IsActive); return Application.Current?.Windows.OfType<ResumeWindow>().FirstOrDefault(w => w.IsActive);
} }
} }
@ -145,11 +145,14 @@ namespace Bit.App
{ {
get get
{ {
return Application.Current.MainPage; return Application.Current?.MainPage;
} }
set set
{ {
Application.Current.MainPage = value; if (Application.Current != null)
{
Application.Current.MainPage = value;
}
} }
} }
#endif #endif

View file

@ -153,7 +153,7 @@ namespace Bit.App.Pages
private async Task StartEnvironmentAsync() private async Task StartEnvironmentAsync()
{ {
await _accountListOverlay.HideAsync(); await _accountListOverlay.HideAsync();
var page = new EnvironmentPage(); var page = new EnvironmentPage();
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }

View file

@ -81,7 +81,7 @@ namespace Bit.App.Pages
{ {
if (message.Command == Constants.ClearSensitiveFields) if (message.Command == Constants.ClearSensitiveFields)
{ {
MainThread.BeginInvokeOnMainThread(_vm.ResetPinPasswordFields); MainThread.BeginInvokeOnMainThread(() => _vm?.ResetPinPasswordFields());
} }
}); });
if (_appeared) if (_appeared)

View file

@ -245,9 +245,9 @@ namespace Bit.App.Pages
public async Task SubmitAsync() public async Task SubmitAsync()
{ {
ShowPassword = false;
try try
{ {
ShowPassword = false;
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinEnabled) if (PinEnabled)
{ {
@ -257,12 +257,15 @@ namespace Bit.App.Pages
{ {
await UnlockWithMasterPasswordAsync(kdfConfig); await UnlockWithMasterPasswordAsync(kdfConfig);
} }
} }
catch (LegacyUserException) catch (LegacyUserException)
{ {
await HandleLegacyUserAsync(); await HandleLegacyUserAsync();
} }
catch (Exception ex)
{
HandleException(ex);
}
} }
private async Task UnlockWithPinAsync(KdfConfig kdfConfig) private async Task UnlockWithPinAsync(KdfConfig kdfConfig)

View file

@ -3,6 +3,7 @@ using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
@ -74,7 +75,7 @@ namespace Bit.App.Pages
{ {
if (message.Command == Constants.ClearSensitiveFields) if (message.Command == Constants.ClearSensitiveFields)
{ {
MainThread.BeginInvokeOnMainThread(_vm.ResetPasswordField); MainThread.BeginInvokeOnMainThread(() => _vm?.ResetPasswordField());
} }
}); });
_mainContent.Content = _mainLayout; _mainContent.Content = _mainLayout;
@ -188,12 +189,20 @@ namespace Bit.App.Pages
private async Task LogInSuccessAsync() private async Task LogInSuccessAsync()
{ {
if (AppHelpers.SetAlternateMainPage(_appOptions)) try
{ {
return; if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
} }
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
} }
private async Task UpdateTempPasswordAsync() private async Task UpdateTempPasswordAsync()

View file

@ -1,6 +1,7 @@
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Services;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -48,12 +49,20 @@ namespace Bit.App.Pages
private async Task LogInSuccessAsync() private async Task LogInSuccessAsync()
{ {
if (AppHelpers.SetAlternateMainPage(_appOptions)) try
{ {
return; if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
} }
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
} }
private async Task UpdateTempPasswordAsync() private async Task UpdateTempPasswordAsync()

View file

@ -1,6 +1,7 @@
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
@ -89,16 +90,30 @@ namespace Bit.App.Pages
private async Task StartTwoFactorAsync() private async Task StartTwoFactorAsync()
{ {
RestoreAppOptionsFromCopy(); try
var page = new TwoFactorPage(true, _appOptions, _vm.OrgIdentifier); {
await Navigation.PushModalAsync(new NavigationPage(page)); RestoreAppOptionsFromCopy();
var page = new TwoFactorPage(true, _appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
private async Task StartSetPasswordAsync() private async Task StartSetPasswordAsync()
{ {
RestoreAppOptionsFromCopy(); try
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier); {
await Navigation.PushModalAsync(new NavigationPage(page)); RestoreAppOptionsFromCopy();
var page = new SetPasswordPage(_appOptions, _vm.OrgIdentifier);
await Navigation.PushModalAsync(new NavigationPage(page));
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
private async Task UpdateTempPasswordAsync() private async Task UpdateTempPasswordAsync()
@ -115,16 +130,23 @@ namespace Bit.App.Pages
private async Task SsoAuthSuccessAsync() private async Task SsoAuthSuccessAsync()
{ {
RestoreAppOptionsFromCopy(); try
await AppHelpers.ClearPreviousPage(); {
RestoreAppOptionsFromCopy();
await AppHelpers.ClearPreviousPage();
if (await _vaultTimeoutService.IsLockedAsync()) if (await _vaultTimeoutService.IsLockedAsync())
{ {
App.MainPage = new NavigationPage(new LockPage(_appOptions)); App.MainPage = new NavigationPage(new LockPage(_appOptions));
}
else
{
App.MainPage = new TabsPage(_appOptions, null);
}
} }
else catch (Exception ex)
{ {
App.MainPage = new TabsPage(_appOptions, null); LoggerHelper.LogEvenIfCantBeResolved(ex);
} }
} }
} }

View file

@ -1,5 +1,6 @@
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Services;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -64,12 +65,19 @@ namespace Bit.App.Pages
private async Task SetPasswordSuccessAsync() private async Task SetPasswordSuccessAsync()
{ {
if (AppHelpers.SetAlternateMainPage(_appOptions)) try
{ {
return; if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
} }
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
} }
} }
} }

View file

@ -2,6 +2,7 @@
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.App.Pages namespace Bit.App.Pages
@ -63,11 +64,11 @@ namespace Bit.App.Pages
if (_vm.YubikeyMethod && !string.IsNullOrWhiteSpace(token) && if (_vm.YubikeyMethod && !string.IsNullOrWhiteSpace(token) &&
token.Length == 44 && !token.Contains(" ")) token.Length == 44 && !token.Contains(" "))
{ {
MainThread.BeginInvokeOnMainThread(async () => MainThread.BeginInvokeOnMainThread(() =>
{ {
_vm.Token = token; _vm.Token = token;
await _vm.SubmitAsync();
}); });
_vm.SubmitCommand.Execute(null);
} }
} }
else if (message.Command == "resumeYubiKey") else if (message.Command == "resumeYubiKey")
@ -124,12 +125,9 @@ namespace Bit.App.Pages
return base.OnBackButtonPressed(); return base.OnBackButtonPressed();
} }
private async void Continue_Clicked(object sender, EventArgs e) private void Continue_Clicked(object sender, EventArgs e)
{ {
if (DoOnce()) _vm.SubmitCommand.Execute(null);
{
await _vm.SubmitAsync();
}
} }
private async void Methods_Clicked(object sender, EventArgs e) private async void Methods_Clicked(object sender, EventArgs e)
@ -158,17 +156,24 @@ namespace Bit.App.Pages
private async void TryAgain_Clicked(object sender, EventArgs e) private async void TryAgain_Clicked(object sender, EventArgs e)
{ {
if (DoOnce()) try
{ {
if (_vm.Fido2Method) if (DoOnce())
{ {
await _vm.Fido2AuthenticateAsync(); if (_vm.Fido2Method)
} {
else if (_vm.YubikeyMethod) await _vm.Fido2AuthenticateAsync();
{ }
_messagingService.Send("listenYubiKeyOTP", true); else if (_vm.YubikeyMethod)
{
_messagingService.Send("listenYubiKeyOTP", true);
}
} }
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
} }
private async Task StartSetPasswordAsync() private async Task StartSetPasswordAsync()

View file

@ -1,25 +1,16 @@
using System; using System.Net;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Services; using Bit.Core.Resources.Localization;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.Maui.Authentication;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class TwoFactorPageViewModel : CaptchaProtectedViewModel public class TwoFactorPageViewModel : CaptchaProtectedViewModel
@ -62,7 +53,7 @@ namespace Bit.App.Pages
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>(); _deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
PageTitle = AppResources.TwoStepLogin; PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync()); SubmitCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(async () => await SubmitAsync()), allowsMultipleExecutions: false);
MoreCommand = CreateDefaultAsyncRelayCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); MoreCommand = CreateDefaultAsyncRelayCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false);
} }
@ -91,8 +82,7 @@ namespace Bit.App.Pages
public bool TotpMethod => AuthenticatorMethod || EmailMethod; public bool TotpMethod => AuthenticatorMethod || EmailMethod;
public bool ShowTryAgain => (YubikeyMethod && // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes public bool ShowTryAgain => (YubikeyMethod && DeviceInfo.Platform == DevicePlatform.iOS) || Fido2Method;
Device.RuntimePlatform == Device.iOS) || Fido2Method;
public bool ShowContinue public bool ShowContinue
{ {
@ -106,9 +96,11 @@ Device.RuntimePlatform == Device.iOS) || Fido2Method;
set => SetProperty(ref _enableContinue, value); set => SetProperty(ref _enableContinue, value);
} }
public string YubikeyInstruction => // TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes #if IOS
Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos : public string YubikeyInstruction => AppResources.YubiKeyInstructionIos;
AppResources.YubiKeyInstruction; #else
public string YubikeyInstruction => AppResources.YubiKeyInstruction;
#endif
public TwoFactorProviderType? SelectedProviderType public TwoFactorProviderType? SelectedProviderType
{ {
@ -124,7 +116,7 @@ Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
nameof(ShowTryAgain), nameof(ShowTryAgain),
}); });
} }
public Command SubmitCommand { get; } public ICommand SubmitCommand { get; }
public ICommand MoreCommand { get; } public ICommand MoreCommand { get; }
public Action TwoFactorAuthSuccessAction { get; set; } public Action TwoFactorAuthSuccessAction { get; set; }
public Action LockAction { get; set; } public Action LockAction { get; set; }
@ -186,7 +178,7 @@ Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
page.DuoWebView.RegisterAction(sig => page.DuoWebView.RegisterAction(sig =>
{ {
Token = sig; Token = sig;
Device.BeginInvokeOnMainThread(async () => await SubmitAsync()); SubmitCommand.Execute(null);
}); });
break; break;
case TwoFactorProviderType.Email: case TwoFactorProviderType.Email:
@ -213,68 +205,76 @@ Device.RuntimePlatform == Device.iOS ? AppResources.YubiKeyInstructionIos :
public async Task Fido2AuthenticateAsync(Dictionary<string, object> providerData = null) public async Task Fido2AuthenticateAsync(Dictionary<string, object> providerData = null)
{ {
await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
if (providerData == null)
{
providerData = _authService.TwoFactorProvidersData[TwoFactorProviderType.Fido2WebAuthn];
}
var callbackUri = "bitwarden://webauthn-callback";
var data = AppHelpers.EncodeDataParameter(new
{
callbackUri = callbackUri,
data = JsonConvert.SerializeObject(providerData),
headerText = AppResources.Fido2Title,
btnText = AppResources.Fido2AuthenticateWebAuthn,
btnReturnText = AppResources.Fido2ReturnToApp,
});
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=2";
WebAuthenticatorResult authResult = null;
try try
{ {
var options = new WebAuthenticatorOptions await _deviceActionService.ShowLoadingAsync(AppResources.Validating);
{
Url = new Uri(url),
CallbackUrl = new Uri(callbackUri),
PrefersEphemeralWebBrowserSession = true,
};
authResult = await WebAuthenticator.AuthenticateAsync(options);
}
catch (TaskCanceledException)
{
// user canceled
await _deviceActionService.HideLoadingAsync();
return;
}
string response = null; if (providerData == null)
if (authResult != null && authResult.Properties.TryGetValue("data", out var resultData))
{
response = Uri.UnescapeDataString(resultData);
}
if (!string.IsNullOrWhiteSpace(response))
{
Token = response;
await SubmitAsync(false);
}
else
{
await _deviceActionService.HideLoadingAsync();
if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError))
{ {
var message = AppResources.Fido2CheckBrowser + "\n\n" + resultError; providerData = _authService.TwoFactorProvidersData[TwoFactorProviderType.Fido2WebAuthn];
await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred, }
AppResources.Ok);
var callbackUri = "bitwarden://webauthn-callback";
var data = AppHelpers.EncodeDataParameter(new
{
callbackUri = callbackUri,
data = JsonConvert.SerializeObject(providerData),
headerText = AppResources.Fido2Title,
btnText = AppResources.Fido2AuthenticateWebAuthn,
btnReturnText = AppResources.Fido2ReturnToApp,
});
var url = _webVaultUrl + "/webauthn-mobile-connector.html?" + "data=" + data +
"&parent=" + Uri.EscapeDataString(callbackUri) + "&v=2";
WebAuthenticatorResult authResult = null;
try
{
var options = new WebAuthenticatorOptions
{
Url = new Uri(url),
CallbackUrl = new Uri(callbackUri),
PrefersEphemeralWebBrowserSession = true,
};
authResult = await WebAuthenticator.AuthenticateAsync(options);
}
catch (TaskCanceledException)
{
// user canceled
await _deviceActionService.HideLoadingAsync();
return;
}
string response = null;
if (authResult != null && authResult.Properties.TryGetValue("data", out var resultData))
{
response = Uri.UnescapeDataString(resultData);
}
if (!string.IsNullOrWhiteSpace(response))
{
Token = response;
await SubmitAsync(false);
} }
else else
{ {
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2CheckBrowser, await _deviceActionService.HideLoadingAsync();
AppResources.AnErrorHasOccurred, AppResources.Ok); if (authResult != null && authResult.Properties.TryGetValue("error", out var resultError))
{
var message = AppResources.Fido2CheckBrowser + "\n\n" + resultError;
await _platformUtilsService.ShowDialogAsync(message, AppResources.AnErrorHasOccurred,
AppResources.Ok);
}
else
{
await _platformUtilsService.ShowDialogAsync(AppResources.Fido2CheckBrowser,
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
} }
}
catch (Exception ex)
{
HandleException(ex);
} }
} }

View file

@ -170,8 +170,15 @@ namespace Bit.App.Pages
{ {
Task.Run(async () => Task.Run(async () =>
{ {
await Task.Delay(ShowModalAnimationDelay); try
MainThread.BeginInvokeOnMainThread(() => input.Focus()); {
await Task.Delay(ShowModalAnimationDelay);
MainThread.BeginInvokeOnMainThread(() => input.Focus());
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}); });
} }

View file

@ -1,5 +1,4 @@
using Bit.App.Abstractions; using Bit.App.Pages;
using Bit.App.Pages;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -13,7 +12,7 @@ namespace Bit.iOS.Core.Handlers
public partial class CustomTabbedHandler : TabbedRenderer public partial class CustomTabbedHandler : TabbedRenderer
{ {
private IBroadcasterService _broadcasterService; private IBroadcasterService _broadcasterService;
private UITabBarItem _previousSelectedItem; private UITabBarItem? _previousSelectedItem;
public CustomTabbedHandler() public CustomTabbedHandler()
{ {
@ -73,8 +72,7 @@ namespace Bit.iOS.Core.Handlers
private void UpdateTabBarAppearance() private void UpdateTabBarAppearance()
{ {
// https://developer.apple.com/forums/thread/682420 // https://developer.apple.com/forums/thread/682420
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); if (UIDevice.CurrentDevice.CheckSystemVersion(15,0))
if (deviceActionService.SystemMajorVersion() >= 15)
{ {
var appearance = new UITabBarAppearance(); var appearance = new UITabBarAppearance();
appearance.ConfigureWithOpaqueBackground(); appearance.ConfigureWithOpaqueBackground();

View file

@ -1,5 +1,4 @@
using System; using System.Diagnostics;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
@ -20,7 +19,7 @@ namespace Bit.iOS.Core.Services
} }
// This gets called a lot - try/catch can be expensive so consider caching or something // This gets called a lot - try/catch can be expensive so consider caching or something
CultureInfo ci = null; CultureInfo? ci;
try try
{ {
ci = new CultureInfo(netLanguage); ci = new CultureInfo(netLanguage);
@ -108,7 +107,7 @@ namespace Bit.iOS.Core.Services
{ {
df.Locale = NSLocale.CurrentLocale; df.Locale = NSLocale.CurrentLocale;
df.DateStyle = NSDateFormatterStyle.Short; df.DateStyle = NSDateFormatterStyle.Short;
return df.StringFor((NSDate)date); return df.StringFor((NSDate?)date);
} }
} }
@ -118,7 +117,7 @@ namespace Bit.iOS.Core.Services
{ {
df.Locale = NSLocale.CurrentLocale; df.Locale = NSLocale.CurrentLocale;
df.TimeStyle = NSDateFormatterStyle.Short; df.TimeStyle = NSDateFormatterStyle.Short;
return df.StringFor((NSDate)time); return df.StringFor((NSDate?)time);
} }
} }
} }

View file

@ -1,8 +1,4 @@
using System; using Foundation;
using System.Collections.Generic;
using System.Linq;
using Bit.Core.Models.Domain;
using Foundation;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Bit.iOS.Core.Utilities namespace Bit.iOS.Core.Utilities
@ -15,6 +11,7 @@ namespace Bit.iOS.Core.Utilities
} }
public static NSDictionary<KTo,VTo> ToNSDictionary<KFrom,VFrom,KTo,VTo>(this Dictionary<KFrom, VFrom> dict, Func<KFrom, KTo> keyConverter, Func<VFrom, VTo> valueConverter) public static NSDictionary<KTo,VTo> ToNSDictionary<KFrom,VFrom,KTo,VTo>(this Dictionary<KFrom, VFrom> dict, Func<KFrom, KTo> keyConverter, Func<VFrom, VTo> valueConverter)
where KFrom : notnull
where KTo : NSObject where KTo : NSObject
where VTo : NSObject where VTo : NSObject
{ {
@ -23,19 +20,20 @@ namespace Bit.iOS.Core.Utilities
return NSDictionary<KTo, VTo>.FromObjectsAndKeys(NSValues, NSKeys, NSKeys.Count()); return NSDictionary<KTo, VTo>.FromObjectsAndKeys(NSValues, NSKeys, NSKeys.Count());
} }
public static Dictionary<string, object> ToDictionary(this NSDictionary<NSString, NSObject> nsDict) public static Dictionary<string, object?> ToDictionary(this NSDictionary<NSString, NSObject> nsDict)
{ {
return nsDict.ToDictionary(v => v?.ToString() as object); return nsDict.ToDictionary(v => v?.ToString());
} }
public static Dictionary<string, object> ToDictionary(this NSDictionary<NSString, NSObject> nsDict, Func<NSObject, object> valueTransformer) public static Dictionary<string, object?> ToDictionary(this NSDictionary<NSString, NSObject> nsDict, Func<NSObject, object?> valueTransformer)
{ {
return nsDict.ToDictionary(k => k.ToString(), v => valueTransformer(v)); return nsDict.ToDictionary(k => k.ToString(), v => valueTransformer(v));
} }
public static Dictionary<KTo, VTo> ToDictionary<KFrom, VFrom, KTo, VTo>(this NSDictionary<KFrom, VFrom> nsDict, Func<KFrom, KTo> keyConverter, Func<VFrom, VTo> valueConverter) public static Dictionary<KTo, VTo?> ToDictionary<KFrom, VFrom, KTo, VTo>(this NSDictionary<KFrom, VFrom> nsDict, Func<KFrom, KTo> keyConverter, Func<VFrom, VTo?> valueConverter)
where KFrom : NSObject where KFrom : NSObject
where VFrom : NSObject where VFrom : NSObject
where KTo : notnull
{ {
var keys = nsDict.Keys.Select(k => keyConverter(k)).ToArray(); var keys = nsDict.Keys.Select(k => keyConverter(k)).ToArray();
var values = nsDict.Values.Select(v => valueConverter(v)).ToArray(); var values = nsDict.Values.Select(v => valueConverter(v)).ToArray();

View file

@ -1,14 +1,8 @@
using System; using System.Diagnostics;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Utilities;
using Foundation; using Foundation;
using Newtonsoft.Json; using Newtonsoft.Json;
using ObjCRuntime;
namespace WatchConnectivity namespace WatchConnectivity
{ {
@ -17,35 +11,45 @@ namespace WatchConnectivity
// Setup is converted from https://www.natashatherobot.com/watchconnectivity-say-hello-to-wcsession/ // Setup is converted from https://www.natashatherobot.com/watchconnectivity-say-hello-to-wcsession/
// with some extra bits // with some extra bits
private static readonly WCSessionManager sharedManager = new WCSessionManager(); private static readonly WCSessionManager sharedManager = new WCSessionManager();
private static WCSession session = WCSession.IsSupported ? WCSession.DefaultSession : null; private static WCSession? session = WCSession.IsSupported ? WCSession.DefaultSession : null;
public event WCSessionReceiveDataHandler OnApplicationContextUpdated; public event WCSessionReceiveDataHandler? OnApplicationContextUpdated;
public event WCSessionReceiveDataHandler OnMessagedReceived; public event WCSessionReceiveDataHandler? OnMessagedReceived;
public delegate void WCSessionReceiveDataHandler(WCSession session, Dictionary<string, object> data); public delegate void WCSessionReceiveDataHandler(WCSession session, Dictionary<string, object?> data);
WCSessionUserInfoTransfer _transf; WCSessionUserInfoTransfer? _transf;
private WCSession validSession private WCSession? validSession
{ {
get get
{ {
if (session is null)
{
return null;
}
Debug.WriteLine($"Paired status:{(session.Paired ? '✓' : '✗')}\n"); Debug.WriteLine($"Paired status:{(session.Paired ? '✓' : '✗')}\n");
Debug.WriteLine($"Watch App Installed status:{(session.WatchAppInstalled ? '✓' : '✗')}\n"); Debug.WriteLine($"Watch App Installed status:{(session.WatchAppInstalled ? '✓' : '✗')}\n");
return (session.Paired && session.WatchAppInstalled) ? session : null; return (session.Paired && session.WatchAppInstalled) ? session : null;
} }
} }
private WCSession validReachableSession private WCSession? validReachableSession
{ {
get get
{ {
if (session is null)
{
return null;
}
return session.Reachable ? validSession : null; return session.Reachable ? validSession : null;
} }
} }
public bool IsValidSession => validSession != null; public bool IsValidSession => validSession != null;
public bool IsSessionReachable => session.Reachable; public bool IsSessionReachable => session?.Reachable ?? false;
public bool IsSessionActivated => validSession?.ActivationState == WCSessionActivationState.Activated; public bool IsSessionActivated => validSession?.ActivationState == WCSessionActivationState.Activated;
@ -71,7 +75,7 @@ namespace WatchConnectivity
public override void SessionReachabilityDidChange(WCSession session) public override void SessionReachabilityDidChange(WCSession session)
{ {
Debug.WriteLine($"Watch connectivity Reachable:{(session.Reachable ? '✓' : '✗')}"); Debug.WriteLine($"Watch connectivity Reachable:{(session?.Reachable == true ? '✓' : '✗')}");
} }
public void SendBackgroundHighPriorityMessage(NSDictionary<NSString, NSObject> applicationContext) public void SendBackgroundHighPriorityMessage(NSDictionary<NSString, NSObject> applicationContext)
@ -102,7 +106,7 @@ namespace WatchConnectivity
public void SendBackgroundFifoHighPriorityMessage(Dictionary<string, object> message) public void SendBackgroundFifoHighPriorityMessage(Dictionary<string, object> message)
{ {
if(validSession is null || validSession.ActivationState != WCSessionActivationState.Activated) if (session is null || validSession is null || validSession.ActivationState != WCSessionActivationState.Activated)
{ {
return; return;
} }
@ -112,6 +116,10 @@ namespace WatchConnectivity
Debug.WriteLine("Started transferring user info"); Debug.WriteLine("Started transferring user info");
_transf = session.TransferUserInfo(message.ToNSDictionary()); _transf = session.TransferUserInfo(message.ToNSDictionary());
if (_transf is null)
{
return;
}
Task.Run(async () => Task.Run(async () =>
{ {
@ -136,7 +144,7 @@ namespace WatchConnectivity
if (OnApplicationContextUpdated != null) if (OnApplicationContextUpdated != null)
{ {
var keys = applicationContext.Keys.Select(k => k.ToString()).ToArray(); var keys = applicationContext.Keys.Select(k => k.ToString()).ToArray();
var values = applicationContext.Values.Select(v => JsonConvert.DeserializeObject(v.ToString())).ToArray(); var values = applicationContext.Values.Select(v => v != null ? JsonConvert.DeserializeObject(v.ToString()) : null).ToArray();
var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v }) var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value); .ToDictionary(x => x.Key, x => x.Value);

View file

@ -116,8 +116,12 @@ namespace Bit.iOS.Core.Utilities
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService()); ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
} }
ILogger logger = null; ILogger? logger = null;
if (ServiceContainer.Resolve<ILogger>("logger", true) == null) if (ServiceContainer.TryResolve<ILogger>(out var resolvedLogger))
{
logger = resolvedLogger;
}
else
{ {
#if DEBUG #if DEBUG
logger = DebugLogger.Instance; logger = DebugLogger.Instance;
@ -129,6 +133,12 @@ namespace Bit.iOS.Core.Utilities
var preferencesStorage = new PreferencesStorageService(AppGroupId); var preferencesStorage = new PreferencesStorageService(AppGroupId);
var appGroupContainer = new NSFileManager().GetContainerUrl(AppGroupId); var appGroupContainer = new NSFileManager().GetContainerUrl(AppGroupId);
if (appGroupContainer?.Path is null)
{
var nreAppGroupContainer = new NullReferenceException("appGroupContainer or its Path is null when registering local services");
logger!.Exception(nreAppGroupContainer);
throw nreAppGroupContainer;
}
var liteDbStorage = new LiteDbStorageService( var liteDbStorage = new LiteDbStorageService(
Path.Combine(appGroupContainer.Path, "Library", "bitwarden.db")); Path.Combine(appGroupContainer.Path, "Library", "bitwarden.db"));
var localizeService = new LocalizeService(); var localizeService = new LocalizeService();
@ -187,14 +197,14 @@ namespace Bit.iOS.Core.Utilities
ServiceContainer.Resolve<ILogger>())); ServiceContainer.Resolve<ILogger>()));
} }
public static void Bootstrap(Func<Task> postBootstrapFunc = null) public static void Bootstrap(Func<Task>? postBootstrapFunc = null)
{ {
var locale = ServiceContainer.Resolve<IStateService>().GetLocale(); var locale = ServiceContainer.Resolve<IStateService>().GetLocale();
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService) (ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService)
.Init(locale != null ? new System.Globalization.CultureInfo(locale) : null); ?.Init(locale != null ? new System.Globalization.CultureInfo(locale) : null);
ServiceContainer.Resolve<IAuthService>("authService").Init(); ServiceContainer.Resolve<IAuthService>("authService").Init();
(ServiceContainer. (ServiceContainer.
Resolve<IPlatformUtilsService>("platformUtilsService") as MobilePlatformUtilsService).Init(); Resolve<IPlatformUtilsService>("platformUtilsService") as MobilePlatformUtilsService)?.Init();
var accountsManager = new AccountsManager( var accountsManager = new AccountsManager(
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"), ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
@ -231,20 +241,31 @@ namespace Bit.iOS.Core.Utilities
if (message.Command == "showDialog") if (message.Command == "showDialog")
{ {
var details = message.Data as DialogDetails; var details = message.Data as DialogDetails;
if (details is null)
{
return;
}
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ? var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText; AppResources.Ok : details.ConfirmText;
NSRunLoop.Main.BeginInvokeOnMainThread(async () => NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{ {
var result = await deviceActionService.DisplayAlertAsync(details.Title, details.Text, try
details.CancelText, confirmText); {
var confirmed = result == details.ConfirmText; var result = await deviceActionService.DisplayAlertAsync(details.Title, details.Text,
messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed)); details.CancelText, confirmText);
var confirmed = result == details.ConfirmText;
messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}); });
} }
else if (message.Command == "listenYubiKeyOTP") else if (message.Command == "listenYubiKeyOTP" && message.Data is bool listen)
{ {
ListenYubiKey((bool)message.Data, deviceActionService, nfcSession, nfcDelegate); ListenYubiKey(listen, deviceActionService, nfcSession, nfcDelegate);
} }
}); });
} }
@ -268,29 +289,36 @@ namespace Bit.iOS.Core.Utilities
} }
} }
private static async Task BootstrapAsync(Func<Task> postBootstrapFunc = null) private static async Task BootstrapAsync(Func<Task>? postBootstrapFunc = null)
{ {
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync(); try
InitializeAppSetup();
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
ServiceContainer.Resolve<IApiService>("apiService"),
ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"),
ServiceContainer.Resolve<ILogger>("logger"));
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
ServiceContainer.Resolve<IUserVerificationService>());
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
if (postBootstrapFunc != null)
{ {
await postBootstrapFunc.Invoke(); await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
InitializeAppSetup();
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
ServiceContainer.Resolve<IApiService>("apiService"),
ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"),
ServiceContainer.Resolve<ILogger>("logger"));
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
ServiceContainer.Resolve<IUserVerificationService>());
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
if (postBootstrapFunc != null)
{
await postBootstrapFunc.Invoke();
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
} }
} }

View file

@ -1,6 +1,4 @@
using System; using Bit.Core.Services;
using System.Threading;
using System.Threading.Tasks;
using Foundation; using Foundation;
using UIKit; using UIKit;
@ -9,7 +7,7 @@ namespace Bit.iOS.Core.Views
public class ExtensionSearchDelegate : UISearchBarDelegate public class ExtensionSearchDelegate : UISearchBarDelegate
{ {
private readonly UITableView _tableView; private readonly UITableView _tableView;
private CancellationTokenSource _filterResultsCancellationTokenSource; private CancellationTokenSource? _filterResultsCancellationTokenSource;
public ExtensionSearchDelegate(UITableView tableView) public ExtensionSearchDelegate(UITableView tableView)
{ {
@ -23,25 +21,34 @@ namespace Bit.iOS.Core.Views
{ {
NSRunLoop.Main.BeginInvokeOnMainThread(async () => NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{ {
if (!string.IsNullOrWhiteSpace(searchText))
{
await Task.Delay(300);
if (searchText != searchBar.Text)
{
return;
}
else
{
_filterResultsCancellationTokenSource?.Cancel();
}
}
try try
{ {
((ExtensionTableSource)_tableView.Source).FilterResults(searchText, cts.Token); if (!string.IsNullOrWhiteSpace(searchText))
_tableView.ReloadData(); {
await Task.Delay(300);
if (searchText != searchBar.Text)
{
return;
}
else
{
_filterResultsCancellationTokenSource?.Cancel();
}
}
try
{
((ExtensionTableSource)_tableView.Source).FilterResults(searchText, cts.Token);
_tableView.ReloadData();
}
catch (OperationCanceledException) { }
_filterResultsCancellationTokenSource = cts;
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
_filterResultsCancellationTokenSource?.Cancel();
cts?.Cancel();
} }
catch (OperationCanceledException) { }
_filterResultsCancellationTokenSource = cts;
}); });
}, cts.Token); }, cts.Token);
} }

View file

@ -479,16 +479,23 @@ namespace Bit.iOS.Extension
{ {
NSRunLoop.Main.BeginInvokeOnMainThread(async () => NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{ {
if (await IsAuthed()) try
{ {
var stateService = ServiceContainer.Resolve<IStateService>("stateService"); if (await IsAuthed())
await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync());
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
if (deviceActionService.SystemMajorVersion() >= 12)
{ {
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); var stateService = ServiceContainer.Resolve<IStateService>("stateService");
await AppHelpers.LogOutAsync(await stateService.GetActiveUserIdAsync());
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
} }
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}); });
} }

View file

@ -274,14 +274,22 @@ namespace Bit.iOS.ShareExtension
{ {
NSRunLoop.Main.BeginInvokeOnMainThread(async () => NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{ {
if (await IsAuthed()) try
{ {
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync()); if (await IsAuthed())
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{ {
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync(); await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
} }
} }
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}); });
} }