diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index 50ad3c773..694619248 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -13,16 +13,10 @@ using System.Reflection; using Xamarin.Forms.Platform.Android; using Xamarin.Forms; using System.Threading.Tasks; -using Bit.App.Models.Page; using Bit.App; using Android.Nfc; -using Android.Views.InputMethods; using System.IO; using System.Linq; -using Android.Views.Autofill; -using Android.App.Assist; -using Bit.Android.Autofill; -using System.Collections.Generic; using Bit.App.Models; using Bit.App.Enums; @@ -35,7 +29,6 @@ namespace Bit.Android public class MainActivity : FormsAppCompatActivity { private const string HockeyAppId = "d3834185b4a643479047b86c65293d42"; - private DateTime? _lastAction; private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$"); private IDeviceActionService _deviceActionService; private ISettings _settings; @@ -97,105 +90,11 @@ namespace Bit.Android if(_appOptions?.Uri == null) { - MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, - "DismissKeyboard", (sender) => DismissKeyboard()); - - MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, - "RateApp", (sender) => RateApp()); - - MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, - "Accessibility", (sender) => OpenAccessibilitySettings()); - - MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, - "LaunchApp", (sender, args) => LaunchApp(args)); - MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, "ListenYubiKeyOTP", (sender, listen) => ListenYubiKey(listen)); - } - MessagingCenter.Subscribe( - Xamarin.Forms.Application.Current, "Autofill", (sender, args) => ReturnCredentials(args)); - - MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, - "BackgroundApp", (sender) => - { - if(Intent.GetBooleanExtra("autofillFramework", false)) - { - SetResult(Result.Canceled); - Finish(); - } - else - { - MoveTaskToBack(true); - } - }); - } - - private void ReturnCredentials(VaultListPageModel.Cipher cipher) - { - if(Intent.GetBooleanExtra("autofillFramework", false)) - { - if(cipher == null) - { - SetResult(Result.Canceled); - Finish(); - return; - } - - var structure = Intent.GetParcelableExtra(AutofillManager.ExtraAssistStructure) as AssistStructure; - if(structure == null) - { - SetResult(Result.Canceled); - Finish(); - return; - } - - var parser = new Parser(structure); - parser.Parse(); - if(!parser.FieldCollection.Fields.Any() || string.IsNullOrWhiteSpace(parser.Uri)) - { - SetResult(Result.Canceled); - Finish(); - return; - } - - var dataset = AutofillHelpers.BuildDataset(this, parser.FieldCollection, new FilledItem(cipher.CipherModel)); - var replyIntent = new Intent(); - replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset); - SetResult(Result.Ok, replyIntent); - Finish(); - } - else - { - var data = new Intent(); - if(cipher == null) - { - data.PutExtra("canceled", "true"); - } - else - { - var isPremium = Resolver.Resolve()?.TokenPremium ?? false; - var autoCopyEnabled = !_settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false); - if(isPremium && autoCopyEnabled && _deviceActionService != null && cipher.LoginTotp?.Value != null) - { - _deviceActionService.CopyToClipboard(App.Utilities.Crypto.Totp(cipher.LoginTotp.Value)); - } - - data.PutExtra("uri", cipher.LoginUri); - data.PutExtra("username", cipher.LoginUsername); - data.PutExtra("password", cipher.LoginPassword?.Value ?? null); - } - - if(Parent == null) - { - SetResult(Result.Ok, data); - } - else - { - Parent.SetResult(Result.Ok, data); - } - - Finish(); + MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, + "FinishMainActivity", (sender) => Finish()); } } @@ -302,65 +201,6 @@ namespace Bit.Android } } - public void RateApp() - { - try - { - var rateIntent = RateIntentForUrl("market://details"); - StartActivity(rateIntent); - } - catch(ActivityNotFoundException) - { - var rateIntent = RateIntentForUrl("https://play.google.com/store/apps/details"); - StartActivity(rateIntent); - } - } - - private Intent RateIntentForUrl(string url) - { - var intent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse($"{url}?id={PackageName}")); - var flags = ActivityFlags.NoHistory | ActivityFlags.MultipleTask; - if((int)Build.VERSION.SdkInt >= 21) - { - flags |= ActivityFlags.NewDocument; - } - else - { - // noinspection deprecation - flags |= ActivityFlags.ClearWhenTaskReset; - } - - intent.AddFlags(flags); - return intent; - } - - private void OpenAccessibilitySettings() - { - var intent = new Intent(global::Android.Provider.Settings.ActionAccessibilitySettings); - StartActivity(intent); - } - - private void LaunchApp(string packageName) - { - if(_lastAction.LastActionWasRecent()) - { - return; - } - _lastAction = DateTime.UtcNow; - - packageName = packageName.Replace("androidapp://", string.Empty); - var launchIntent = PackageManager.GetLaunchIntentForPackage(packageName); - if(launchIntent == null) - { - var dialog = Resolver.Resolve(); - dialog.Alert(string.Format(App.Resources.AppResources.CannotOpenApp, packageName)); - } - else - { - StartActivity(launchIntent); - } - } - private void ListenYubiKey(bool listen) { if(!Utilities.NfcEnabled()) @@ -405,16 +245,6 @@ namespace Bit.Android } } - private void DismissKeyboard() - { - try - { - var imm = (InputMethodManager)GetSystemService(InputMethodService); - imm.HideSoftInputFromWindow(CurrentFocus.WindowToken, 0); - } - catch { } - } - private AppOptions GetOptions() { var options = new AppOptions diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index feb15793a..fd92ee235 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -15,18 +15,32 @@ using System.Collections.Generic; using Android; using Android.Content.PM; using Android.Support.V4.App; +using Bit.App.Models.Page; +using XLabs.Ioc; +using Android.App; +using Android.Views.Autofill; +using Android.App.Assist; +using Bit.Android.Autofill; +using System.Linq; +using Plugin.Settings.Abstractions; +using Acr.UserDialogs; +using Android.Views.InputMethods; namespace Bit.Android.Services { public class DeviceActionService : IDeviceActionService { private readonly IAppSettingsService _appSettingsService; + private readonly IUserDialogs _userDialogs; private bool _cameraPermissionsDenied; + private DateTime? _lastAction; public DeviceActionService( - IAppSettingsService appSettingsService) + IAppSettingsService appSettingsService, + IUserDialogs userDialogs) { _appSettingsService = appSettingsService; + _userDialogs = userDialogs; } public void CopyToClipboard(string text) @@ -109,34 +123,10 @@ namespace Bit.Android.Services catch(Exception) { } } - private bool DeleteDir(Java.IO.File dir) - { - if(dir != null && dir.IsDirectory) - { - var children = dir.List(); - for(int i = 0; i < children.Length; i++) - { - var success = DeleteDir(new Java.IO.File(dir, children[i])); - if(!success) - { - return false; - } - } - return dir.Delete(); - } - else if(dir != null && dir.IsFile) - { - return dir.Delete(); - } - else - { - return false; - } - } - public Task SelectFileAsync() { - MessagingCenter.Unsubscribe(Application.Current, "SelectFileCameraPermissionDenied"); + MessagingCenter.Unsubscribe(Xamarin.Forms.Application.Current, + "SelectFileCameraPermissionDenied"); var hasStorageWritePermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.WriteExternalStorage); @@ -189,6 +179,195 @@ namespace Bit.Android.Services return Task.FromResult(0); } + public void Autofill(VaultListPageModel.Cipher cipher) + { + var activity = (MainActivity)Forms.Context; + if(activity.Intent.GetBooleanExtra("autofillFramework", false)) + { + if(cipher == null) + { + activity.SetResult(Result.Canceled); + activity.Finish(); + return; + } + + var structure = activity.Intent.GetParcelableExtra( + AutofillManager.ExtraAssistStructure) as AssistStructure; + if(structure == null) + { + activity.SetResult(Result.Canceled); + activity.Finish(); + return; + } + + var parser = new Parser(structure); + parser.Parse(); + if(!parser.FieldCollection.Fields.Any() || string.IsNullOrWhiteSpace(parser.Uri)) + { + activity.SetResult(Result.Canceled); + activity.Finish(); + return; + } + + var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, + new FilledItem(cipher.CipherModel)); + var replyIntent = new Intent(); + replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset); + activity.SetResult(Result.Ok, replyIntent); + activity.Finish(); + } + else + { + var data = new Intent(); + if(cipher == null) + { + data.PutExtra("canceled", "true"); + } + else + { + var isPremium = Resolver.Resolve()?.TokenPremium ?? false; + var settings = Resolver.Resolve(); + var autoCopyEnabled = !settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false); + if(isPremium && autoCopyEnabled && cipher.LoginTotp?.Value != null) + { + CopyToClipboard(App.Utilities.Crypto.Totp(cipher.LoginTotp.Value)); + } + + data.PutExtra("uri", cipher.LoginUri); + data.PutExtra("username", cipher.LoginUsername); + data.PutExtra("password", cipher.LoginPassword?.Value ?? null); + } + + if(activity.Parent == null) + { + activity.SetResult(Result.Ok, data); + } + else + { + activity.Parent.SetResult(Result.Ok, data); + } + + activity.Finish(); + MessagingCenter.Send(Xamarin.Forms.Application.Current, "FinishMainActivity"); + } + } + + public void CloseAutofill() + { + Autofill(null); + } + + public void Background() + { + var activity = (MainActivity)Forms.Context; + if(activity.Intent.GetBooleanExtra("autofillFramework", false)) + { + activity.SetResult(Result.Canceled); + activity.Finish(); + } + else + { + activity.MoveTaskToBack(true); + } + } + + public void RateApp() + { + var activity = (MainActivity)Forms.Context; + try + { + var rateIntent = RateIntentForUrl("market://details", activity); + activity.StartActivity(rateIntent); + } + catch(ActivityNotFoundException) + { + var rateIntent = RateIntentForUrl("https://play.google.com/store/apps/details", activity); + activity.StartActivity(rateIntent); + } + } + + public void DismissKeyboard() + { + var activity = (MainActivity)Forms.Context; + try + { + var imm = (InputMethodManager)activity.GetSystemService(Context.InputMethodService); + imm.HideSoftInputFromWindow(activity.CurrentFocus.WindowToken, 0); + } + catch { } + } + + public void OpenAccessibilitySettings() + { + var activity = (MainActivity)Forms.Context; + var intent = new Intent(Settings.ActionAccessibilitySettings); + activity.StartActivity(intent); + } + + public void LaunchApp(string appName) + { + var activity = (MainActivity)Forms.Context; + if(_lastAction.LastActionWasRecent()) + { + return; + } + _lastAction = DateTime.UtcNow; + + appName = appName.Replace("androidapp://", string.Empty); + var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName); + if(launchIntent == null) + { + _userDialogs.Alert(string.Format(AppResources.CannotOpenApp, appName)); + } + else + { + activity.StartActivity(launchIntent); + } + } + + private Intent RateIntentForUrl(string url, Activity activity) + { + var intent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse($"{url}?id={activity.PackageName}")); + var flags = ActivityFlags.NoHistory | ActivityFlags.MultipleTask; + if((int)Build.VERSION.SdkInt >= 21) + { + flags |= ActivityFlags.NewDocument; + } + else + { + // noinspection deprecation + flags |= ActivityFlags.ClearWhenTaskReset; + } + + intent.AddFlags(flags); + return intent; + } + + private bool DeleteDir(Java.IO.File dir) + { + if(dir != null && dir.IsDirectory) + { + var children = dir.List(); + for(int i = 0; i < children.Length; i++) + { + var success = DeleteDir(new Java.IO.File(dir, children[i])); + if(!success) + { + return false; + } + } + return dir.Delete(); + } + else if(dir != null && dir.IsFile) + { + return dir.Delete(); + } + else + { + return false; + } + } + private List GetCameraIntents(global::Android.Net.Uri outputUri) { var intents = new List(); @@ -214,10 +393,11 @@ namespace Bit.Android.Services private void AskCameraPermission(string permission) { - MessagingCenter.Subscribe(Application.Current, "SelectFileCameraPermissionDenied", (sender) => - { - _cameraPermissionsDenied = true; - }); + MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, + "SelectFileCameraPermissionDenied", (sender) => + { + _cameraPermissionsDenied = true; + }); AskPermission(permission); } diff --git a/src/Android/Services/GoogleAnalyticsService.cs b/src/Android/Services/GoogleAnalyticsService.cs index da03cb5eb..309551717 100644 --- a/src/Android/Services/GoogleAnalyticsService.cs +++ b/src/Android/Services/GoogleAnalyticsService.cs @@ -10,17 +10,13 @@ namespace Bit.Android.Services public class GoogleAnalyticsService : IGoogleAnalyticsService { private readonly GoogleAnalytics _instance; - private readonly IAuthService _authService; private readonly Tracker _tracker; public GoogleAnalyticsService( Context appContext, IAppIdService appIdService, - IAuthService authService, ISettings settings) { - _authService = authService; - _instance = GoogleAnalytics.GetInstance(appContext.ApplicationContext); _instance.SetLocalDispatchPeriod(10); diff --git a/src/App/Abstractions/Services/IAuthService.cs b/src/App/Abstractions/Services/IAuthService.cs index 2dc25591e..5f6e542a4 100644 --- a/src/App/Abstractions/Services/IAuthService.cs +++ b/src/App/Abstractions/Services/IAuthService.cs @@ -12,9 +12,8 @@ namespace Bit.App.Abstractions bool UserIdChanged { get; } string Email { get; set; } string PIN { get; set; } - bool BelongsToOrganization(string orgId); - void LogOut(); + void LogOut(string logoutMessage = null); Task TokenPostAsync(string email, string masterPassword); Task TokenPostTwoFactorAsync(TwoFactorProviderType type, string token, bool remember, string email, string masterPasswordHash, SymmetricCryptoKey key); diff --git a/src/App/Abstractions/Services/IDeviceActionService.cs b/src/App/Abstractions/Services/IDeviceActionService.cs index 9d82d1d72..e4b83aa16 100644 --- a/src/App/Abstractions/Services/IDeviceActionService.cs +++ b/src/App/Abstractions/Services/IDeviceActionService.cs @@ -10,5 +10,12 @@ namespace Bit.App.Abstractions bool CanOpenFile(string fileName); Task SelectFileAsync(); void ClearCache(); + void Autofill(Models.Page.VaultListPageModel.Cipher cipher); + void CloseAutofill(); + void Background(); + void RateApp(); + void DismissKeyboard(); + void OpenAccessibilitySettings(); + void LaunchApp(string appName); } } diff --git a/src/App/Abstractions/Services/ILockService.cs b/src/App/Abstractions/Services/ILockService.cs index 95d823c7d..a91ebe0ab 100644 --- a/src/App/Abstractions/Services/ILockService.cs +++ b/src/App/Abstractions/Services/ILockService.cs @@ -8,5 +8,7 @@ namespace Bit.App.Abstractions { void UpdateLastActivity(DateTime? activityDate = null); Task GetLockTypeAsync(bool forceLock); + Task CheckLockAsync(bool forceLock); + bool TopPageIsLock(); } } \ No newline at end of file diff --git a/src/App/App.cs b/src/App/App.cs index 572b29637..c302f744a 100644 --- a/src/App/App.cs +++ b/src/App/App.cs @@ -86,27 +86,20 @@ namespace Bit.App MainPage = new ExtendedNavigationPage(new HomePage()); } - MessagingCenter.Subscribe(Current, "Resumed", async (sender, args) => + if(Device.RuntimePlatform == Device.iOS) { - Device.BeginInvokeOnMainThread(async () => await CheckLockAsync(args)); - await Task.Run(() => FullSyncAsync()).ConfigureAwait(false); - }); - - MessagingCenter.Subscribe(Current, "Lock", (sender, args) => - { - Device.BeginInvokeOnMainThread(async () => await CheckLockAsync(args)); - }); - - MessagingCenter.Subscribe(Current, "Logout", (sender, args) => - { - Logout(args); - }); + MessagingCenter.Subscribe(Current, "Resumed", async (sender, args) => + { + Device.BeginInvokeOnMainThread(async () => await _lockService.CheckLockAsync(args)); + await Task.Run(() => FullSyncAsync()).ConfigureAwait(false); + }); + } } protected async override void OnStart() { // Handle when your app starts - await CheckLockAsync(false); + await _lockService.CheckLockAsync(false); if(string.IsNullOrWhiteSpace(_options.Uri)) { @@ -132,7 +125,7 @@ namespace Bit.App SetMainPageFromAutofill(); - if(Device.RuntimePlatform == Device.Android && !TopPageIsLock()) + if(Device.RuntimePlatform == Device.Android && !_lockService.TopPageIsLock()) { _lockService.UpdateLastActivity(); } @@ -151,7 +144,7 @@ namespace Bit.App if(Device.RuntimePlatform == Device.Android) { - await CheckLockAsync(false); + await _lockService.CheckLockAsync(false); } var lockPinPage = Current.MainPage.Navigation.ModalStack.LastOrDefault() as LockPinPage; @@ -227,73 +220,6 @@ namespace Bit.App } } - private void Logout(string logoutMessage) - { - _authService.LogOut(); - - var deviceApiRepository = Resolver.Resolve(); - var appIdService = Resolver.Resolve(); - Task.Run(async () => await deviceApiRepository.PutClearTokenAsync(appIdService.AppId)); - - _googleAnalyticsService.TrackAppEvent("LoggedOut"); - - Device.BeginInvokeOnMainThread(() => Current.MainPage = new ExtendedNavigationPage(new HomePage())); - if(!string.IsNullOrWhiteSpace(logoutMessage)) - { - _userDialogs.Toast(logoutMessage); - } - } - - private async Task CheckLockAsync(bool forceLock) - { - if(TopPageIsLock()) - { - // already locked - return; - } - - var lockType = await _lockService.GetLockTypeAsync(forceLock); - if(lockType == Enums.LockType.None) - { - return; - } - - _appSettingsService.Locked = true; - switch(lockType) - { - case Enums.LockType.Fingerprint: - await Current.MainPage.Navigation.PushModalAsync(new ExtendedNavigationPage(new LockFingerprintPage(!forceLock)), false); - break; - case Enums.LockType.PIN: - await Current.MainPage.Navigation.PushModalAsync(new ExtendedNavigationPage(new LockPinPage()), false); - break; - case Enums.LockType.Password: - await Current.MainPage.Navigation.PushModalAsync(new ExtendedNavigationPage(new LockPasswordPage()), false); - break; - default: - break; - } - } - - private bool TopPageIsLock() - { - var currentPage = Current.MainPage.Navigation.ModalStack.LastOrDefault() as ExtendedNavigationPage; - if((currentPage?.CurrentPage as LockFingerprintPage) != null) - { - return true; - } - if((currentPage?.CurrentPage as LockPinPage) != null) - { - return true; - } - if((currentPage?.CurrentPage as LockPasswordPage) != null) - { - return true; - } - - return false; - } - private void SetStyles() { var gray = Color.FromHex("333333"); diff --git a/src/App/Controls/ExtendedContentPage.cs b/src/App/Controls/ExtendedContentPage.cs index 4e0a8f301..678d32c65 100644 --- a/src/App/Controls/ExtendedContentPage.cs +++ b/src/App/Controls/ExtendedContentPage.cs @@ -1,6 +1,4 @@ using Bit.App.Abstractions; -using Plugin.Settings.Abstractions; -using System; using Xamarin.Forms; using XLabs.Ioc; @@ -11,6 +9,7 @@ namespace Bit.App.Controls private ISyncService _syncService; private IGoogleAnalyticsService _googleAnalyticsService; private ILockService _lockService; + private IDeviceActionService _deviceActionService; private bool _syncIndicator; private bool _updateActivity; @@ -21,25 +20,21 @@ namespace Bit.App.Controls _syncService = Resolver.Resolve(); _googleAnalyticsService = Resolver.Resolve(); _lockService = Resolver.Resolve(); + _deviceActionService = Resolver.Resolve(); BackgroundColor = Color.FromHex("efeff4"); - - if(_syncIndicator) - { - MessagingCenter.Subscribe(Application.Current, "SyncCompleted", (sender, success) => - { - Device.BeginInvokeOnMainThread(() => IsBusy = _syncService.SyncInProgress); - }); - - MessagingCenter.Subscribe(Application.Current, "SyncStarted", (sender) => - { - Device.BeginInvokeOnMainThread(() => IsBusy = _syncService.SyncInProgress); - }); - } } protected override void OnAppearing() { + if(_syncIndicator) + { + MessagingCenter.Subscribe(_syncService, "SyncCompleted", + (sender, success) => Device.BeginInvokeOnMainThread(() => IsBusy = _syncService.SyncInProgress)); + MessagingCenter.Subscribe(_syncService, "SyncStarted", + (sender) => Device.BeginInvokeOnMainThread(() => IsBusy = _syncService.SyncInProgress)); + } + if(_syncIndicator) { IsBusy = _syncService.SyncInProgress; @@ -51,6 +46,12 @@ namespace Bit.App.Controls protected override void OnDisappearing() { + if(_syncIndicator) + { + MessagingCenter.Unsubscribe(_syncService, "SyncCompleted"); + MessagingCenter.Unsubscribe(_syncService, "SyncStarted"); + } + if(_syncIndicator) { IsBusy = false; @@ -62,7 +63,7 @@ namespace Bit.App.Controls } base.OnDisappearing(); - MessagingCenter.Send(Application.Current, "DismissKeyboard"); + _deviceActionService.DismissKeyboard(); } } } diff --git a/src/App/Pages/Lock/BaseLockPage.cs b/src/App/Pages/Lock/BaseLockPage.cs index 027cbf998..1cd5eff5f 100644 --- a/src/App/Pages/Lock/BaseLockPage.cs +++ b/src/App/Pages/Lock/BaseLockPage.cs @@ -4,27 +4,28 @@ using Bit.App.Controls; using Bit.App.Resources; using Xamarin.Forms; using XLabs.Ioc; +using Bit.App.Abstractions; namespace Bit.App.Pages { public class BaseLockPage : ExtendedContentPage { + private readonly IDeviceActionService _deviceActionService; + public BaseLockPage() : base(false, false) { - UserDialogs = Resolver.Resolve(); + AuthService = Resolver.Resolve(); + _deviceActionService = Resolver.Resolve(); } protected IUserDialogs UserDialogs { get; set; } + protected IAuthService AuthService { get; set; } protected override bool OnBackButtonPressed() { - if(Device.RuntimePlatform == Device.Android) - { - MessagingCenter.Send(Application.Current, "BackgroundApp"); - } - + _deviceActionService.Background(); return true; } @@ -34,8 +35,7 @@ namespace Bit.App.Pages { return; } - - MessagingCenter.Send(Application.Current, "Logout", (string)null); + AuthService.LogOut(); } } } diff --git a/src/App/Pages/Lock/LockFingerprintPage.cs b/src/App/Pages/Lock/LockFingerprintPage.cs index ce9aa2995..24c1eba3c 100644 --- a/src/App/Pages/Lock/LockFingerprintPage.cs +++ b/src/App/Pages/Lock/LockFingerprintPage.cs @@ -100,7 +100,7 @@ namespace Bit.App.Pages } else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + AuthService.LogOut(); } } } diff --git a/src/App/Pages/LoginTwoFactorPage.cs b/src/App/Pages/LoginTwoFactorPage.cs index 151a373e2..18bc7a66d 100644 --- a/src/App/Pages/LoginTwoFactorPage.cs +++ b/src/App/Pages/LoginTwoFactorPage.cs @@ -22,6 +22,7 @@ namespace Bit.App.Pages private IUserDialogs _userDialogs; private ISyncService _syncService; private IDeviceInfoService _deviceInfoService; + private IDeviceActionService _deviceActionService; private IGoogleAnalyticsService _googleAnalyticsService; private ITwoFactorApiRepository _twoFactorApiRepository; private IPushNotificationService _pushNotification; @@ -45,6 +46,7 @@ namespace Bit.App.Pages _providers = result.TwoFactorProviders; _providerType = type ?? GetDefaultProvider(); + _deviceActionService = Resolver.Resolve(); _authService = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); _syncService = Resolver.Resolve(); @@ -266,7 +268,7 @@ namespace Bit.App.Pages InitEvents(); if(TokenCell == null && Device.RuntimePlatform == Device.Android) { - MessagingCenter.Send(Application.Current, "DismissKeyboard"); + _deviceActionService.DismissKeyboard(); } } diff --git a/src/App/Pages/Settings/SettingsPage.cs b/src/App/Pages/Settings/SettingsPage.cs index 9515b1d84..1134c17f5 100644 --- a/src/App/Pages/Settings/SettingsPage.cs +++ b/src/App/Pages/Settings/SettingsPage.cs @@ -19,7 +19,8 @@ namespace Bit.App.Pages private readonly IFingerprint _fingerprint; private readonly IPushNotificationService _pushNotification; private readonly IGoogleAnalyticsService _googleAnalyticsService; - private readonly IDeviceInfoService _deviceInfoService; + private readonly IDeviceActionService _deviceActionService; + private readonly ILockService _lockService; // TODO: Model binding context? @@ -31,7 +32,8 @@ namespace Bit.App.Pages _fingerprint = Resolver.Resolve(); _pushNotification = Resolver.Resolve(); _googleAnalyticsService = Resolver.Resolve(); - _deviceInfoService = Resolver.Resolve(); + _deviceActionService = Resolver.Resolve(); + _lockService = Resolver.Resolve(); Init(); } @@ -327,22 +329,7 @@ namespace Bit.App.Pages private void RateCell_Tapped(object sender, EventArgs e) { _googleAnalyticsService.TrackAppEvent("OpenedSetting", "RateApp"); - if(Device.RuntimePlatform == Device.iOS) - { - if(_deviceInfoService.Version < 11) - { - Device.OpenUri(new Uri("itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews" + - "?id=1137397744&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software")); - } - else - { - Device.OpenUri(new Uri("itms-apps://itunes.apple.com/us/app/id1137397744?action=write-review")); - } - } - else if(Device.RuntimePlatform == Device.Android) - { - MessagingCenter.Send(Application.Current, "RateApp"); - } + _deviceActionService.RateApp(); } private void HelpCell_Tapped(object sender, EventArgs e) @@ -353,7 +340,7 @@ namespace Bit.App.Pages private void LockCell_Tapped(object sender, EventArgs e) { _googleAnalyticsService.TrackAppEvent("Locked"); - MessagingCenter.Send(Application.Current, "Lock", true); + Device.BeginInvokeOnMainThread(async () => await _lockService.CheckLockAsync(true)); } private async void LogOutCell_Tapped(object sender, EventArgs e) @@ -363,7 +350,7 @@ namespace Bit.App.Pages return; } - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } private async void ChangeMasterPasswordCell_Tapped(object sender, EventArgs e) diff --git a/src/App/Pages/Tools/ToolsAutofillServicePage.cs b/src/App/Pages/Tools/ToolsAutofillServicePage.cs index 6490f4b5d..63e2be338 100644 --- a/src/App/Pages/Tools/ToolsAutofillServicePage.cs +++ b/src/App/Pages/Tools/ToolsAutofillServicePage.cs @@ -12,12 +12,14 @@ namespace Bit.App.Pages { private readonly IGoogleAnalyticsService _googleAnalyticsService; private readonly IAppInfoService _appInfoService; + private readonly IDeviceActionService _deviceActionService; private bool _pageDisappeared = false; public ToolsAutofillServicePage() { _googleAnalyticsService = Resolver.Resolve(); _appInfoService = Resolver.Resolve(); + _deviceActionService = Resolver.Resolve(); Init(); } @@ -221,7 +223,7 @@ namespace Bit.App.Pages Command = new Command(() => { _googleAnalyticsService.TrackAppEvent("OpenAccessibilitySettings"); - MessagingCenter.Send(Application.Current, "Accessibility"); + _deviceActionService.OpenAccessibilitySettings(); }), VerticalOptions = LayoutOptions.End, HorizontalOptions = LayoutOptions.Fill, diff --git a/src/App/Pages/Vault/VaultAddCipherPage.cs b/src/App/Pages/Vault/VaultAddCipherPage.cs index a8ecf6ed0..5c0be8e23 100644 --- a/src/App/Pages/Vault/VaultAddCipherPage.cs +++ b/src/App/Pages/Vault/VaultAddCipherPage.cs @@ -29,6 +29,7 @@ namespace Bit.App.Pages private readonly ISettings _settings; private readonly IAppInfoService _appInfoService; private readonly IDeviceInfoService _deviceInfo; + private readonly IDeviceActionService _deviceActionService; private readonly string _defaultUri; private readonly string _defaultName; private readonly string _defaultUsername; @@ -75,6 +76,7 @@ namespace Bit.App.Pages _settings = Resolver.Resolve(); _appInfoService = Resolver.Resolve(); _deviceInfo = Resolver.Resolve(); + _deviceActionService = Resolver.Resolve(); if(doInit) { @@ -751,7 +753,7 @@ namespace Bit.App.Pages if(_fromAutofillFramework) { // close and go back to app - MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Cipher)null); + _deviceActionService.CloseAutofill(); } else { diff --git a/src/App/Pages/Vault/VaultAutofillListCiphersPage.cs b/src/App/Pages/Vault/VaultAutofillListCiphersPage.cs index 5f1ba98f0..bd4b1015a 100644 --- a/src/App/Pages/Vault/VaultAutofillListCiphersPage.cs +++ b/src/App/Pages/Vault/VaultAutofillListCiphersPage.cs @@ -20,7 +20,7 @@ namespace Bit.App.Pages { private readonly ICipherService _cipherService; private readonly IDeviceInfoService _deviceInfoService; - private readonly IDeviceActionService _clipboardService; + private readonly IDeviceActionService _deviceActionService; private readonly ISettingsService _settingsService; private readonly IAppSettingsService _appSettingsService; private CancellationTokenSource _filterResultsCancellationTokenSource; @@ -44,7 +44,7 @@ namespace Bit.App.Pages _cipherService = Resolver.Resolve(); _deviceInfoService = Resolver.Resolve(); - _clipboardService = Resolver.Resolve(); + _deviceActionService = Resolver.Resolve(); _settingsService = Resolver.Resolve(); UserDialogs = Resolver.Resolve(); _appSettingsService = Resolver.Resolve(); @@ -141,7 +141,7 @@ namespace Bit.App.Pages protected override bool OnBackButtonPressed() { GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App"); - MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Cipher)null); + _deviceActionService.CloseAutofill(); return true; } @@ -243,7 +243,7 @@ namespace Bit.App.Pages if(doAutofill) { GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App"); - MessagingCenter.Send(Application.Current, "Autofill", cipher as VaultListPageModel.Cipher); + _deviceActionService.Autofill(cipher); } } @@ -322,7 +322,7 @@ namespace Bit.App.Pages private void Copy(string copyText, string alertLabel) { - _clipboardService.CopyToClipboard(copyText); + _deviceActionService.CopyToClipboard(copyText); UserDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel)); } diff --git a/src/App/Pages/Vault/VaultListCiphersPage.cs b/src/App/Pages/Vault/VaultListCiphersPage.cs index 697cc58e9..2a7fbf648 100644 --- a/src/App/Pages/Vault/VaultListCiphersPage.cs +++ b/src/App/Pages/Vault/VaultListCiphersPage.cs @@ -24,7 +24,7 @@ namespace Bit.App.Pages private readonly ICipherService _cipherService; private readonly IUserDialogs _userDialogs; private readonly IConnectivity _connectivity; - private readonly IDeviceActionService _clipboardService; + private readonly IDeviceActionService _deviceActionService; private readonly ISyncService _syncService; private readonly IPushNotificationService _pushNotification; private readonly IDeviceInfoService _deviceInfoService; @@ -42,7 +42,7 @@ namespace Bit.App.Pages _cipherService = Resolver.Resolve(); _connectivity = Resolver.Resolve(); _userDialogs = Resolver.Resolve(); - _clipboardService = Resolver.Resolve(); + _deviceActionService = Resolver.Resolve(); _syncService = Resolver.Resolve(); _pushNotification = Resolver.Resolve(); _deviceInfoService = Resolver.Resolve(); @@ -71,14 +71,6 @@ namespace Bit.App.Pages private void Init() { - MessagingCenter.Subscribe(Application.Current, "SyncCompleted", (sender, success) => - { - if(success) - { - _filterResultsCancellationTokenSource = FetchAndLoadVault(); - } - }); - if(!_favorites) { AddCipherItem = new AddCipherToolBarItem(this); @@ -232,6 +224,14 @@ namespace Bit.App.Pages protected override void OnAppearing() { base.OnAppearing(); + MessagingCenter.Subscribe(_syncService, "SyncCompleted", (sender, success) => + { + if(success) + { + _filterResultsCancellationTokenSource = FetchAndLoadVault(); + } + }); + ListView.ItemSelected += CipherSelected; Search.TextChanged += SearchBar_TextChanged; Search.SearchButtonPressed += SearchBar_SearchButtonPressed; @@ -274,6 +274,8 @@ namespace Bit.App.Pages protected override void OnDisappearing() { base.OnDisappearing(); + MessagingCenter.Unsubscribe(_syncService, "SyncCompleted"); + ListView.ItemSelected -= CipherSelected; Search.TextChanged -= SearchBar_TextChanged; Search.SearchButtonPressed -= SearchBar_SearchButtonPressed; @@ -288,7 +290,7 @@ namespace Bit.App.Pages } _googleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App"); - MessagingCenter.Send(Application.Current, "Autofill", (VaultListPageModel.Cipher)null); + _deviceActionService.CloseAutofill(); return true; } @@ -417,7 +419,7 @@ namespace Bit.App.Pages { _googleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App"); - MessagingCenter.Send(Application.Current, "Autofill", cipher); + _deviceActionService.Autofill(cipher); } } @@ -492,7 +494,7 @@ namespace Bit.App.Pages private void Copy(string copyText, string alertLabel) { - _clipboardService.CopyToClipboard(copyText); + _deviceActionService.CopyToClipboard(copyText); _userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel)); } diff --git a/src/App/Pages/Vault/VaultViewCipherPage.cs b/src/App/Pages/Vault/VaultViewCipherPage.cs index e3257ef1e..4eb8d8486 100644 --- a/src/App/Pages/Vault/VaultViewCipherPage.cs +++ b/src/App/Pages/Vault/VaultViewCipherPage.cs @@ -136,7 +136,7 @@ namespace Bit.App.Pages { if(Device.RuntimePlatform == Device.Android && Model.LoginUri.StartsWith("androidapp://")) { - MessagingCenter.Send(Application.Current, "LaunchApp", Model.LoginUri); + _deviceActionService.LaunchApp(Model.LoginUri); } else if(Model.LoginUri.StartsWith("http://") || Model.LoginUri.StartsWith("https://")) { diff --git a/src/App/Services/AuthService.cs b/src/App/Services/AuthService.cs index 99d735704..e73082537 100644 --- a/src/App/Services/AuthService.cs +++ b/src/App/Services/AuthService.cs @@ -7,6 +7,10 @@ using Plugin.Settings.Abstractions; using Bit.App.Models; using System.Linq; using Bit.App.Enums; +using Xamarin.Forms; +using Bit.App.Pages; +using Bit.App.Controls; +using Acr.UserDialogs; namespace Bit.App.Services { @@ -25,6 +29,9 @@ namespace Bit.App.Services private readonly IAccountsApiRepository _accountsApiRepository; private readonly IAppIdService _appIdService; private readonly IDeviceInfoService _deviceInfoService; + private readonly IDeviceApiRepository _deviceApiRepository; + private readonly IGoogleAnalyticsService _googleAnalyticsService; + private readonly IUserDialogs _userDialogs; private string _email; private string _userId; @@ -39,7 +46,10 @@ namespace Bit.App.Services IConnectApiRepository connectApiRepository, IAccountsApiRepository accountsApiRepository, IAppIdService appIdService, - IDeviceInfoService deviceInfoService) + IDeviceInfoService deviceInfoService, + IDeviceApiRepository deviceApiRepository, + IGoogleAnalyticsService googleAnalyticsService, + IUserDialogs userDialogs) { _secureStorage = secureStorage; _tokenService = tokenService; @@ -49,6 +59,9 @@ namespace Bit.App.Services _accountsApiRepository = accountsApiRepository; _appIdService = appIdService; _deviceInfoService = deviceInfoService; + _deviceApiRepository = deviceApiRepository; + _googleAnalyticsService = googleAnalyticsService; + _userDialogs = userDialogs; } public string UserId @@ -194,7 +207,7 @@ namespace Bit.App.Services return !string.IsNullOrWhiteSpace(orgId) && (_cryptoService.OrgKeys?.ContainsKey(orgId) ?? false); } - public void LogOut() + public void LogOut(string logoutMessage = null) { _tokenService.Token = null; _tokenService.RefreshToken = null; @@ -204,6 +217,16 @@ namespace Bit.App.Services _settings.Remove(Constants.SecurityStamp); _settings.Remove(Constants.PushLastRegistrationDate); _settings.Remove(Constants.Locked); + + Task.Run(async () => await _deviceApiRepository.PutClearTokenAsync(_appIdService.AppId)); + + _googleAnalyticsService.TrackAppEvent("LoggedOut"); + + Device.BeginInvokeOnMainThread(() => Application.Current.MainPage = new ExtendedNavigationPage(new HomePage())); + if(!string.IsNullOrWhiteSpace(logoutMessage)) + { + _userDialogs.Toast(logoutMessage); + } } public async Task TokenPostAsync(string email, string masterPassword) @@ -249,10 +272,10 @@ namespace Bit.App.Services return result; } - public async Task TokenPostTwoFactorAsync(TwoFactorProviderType type, string token, bool remember, + public async Task TokenPostTwoFactorAsync(TwoFactorProviderType type, string token, bool remember, string email, string masterPasswordHash, SymmetricCryptoKey key) { - var result = new LoginResult(); + var result = new Models.LoginResult(); var request = new TokenRequest { diff --git a/src/App/Services/CipherService.cs b/src/App/Services/CipherService.cs index 5803f20f3..47ce79dd0 100644 --- a/src/App/Services/CipherService.cs +++ b/src/App/Services/CipherService.cs @@ -250,7 +250,7 @@ namespace Bit.App.Services else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden || response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } return response; @@ -272,7 +272,7 @@ namespace Bit.App.Services else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden || response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } return response; @@ -334,7 +334,7 @@ namespace Bit.App.Services else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden || response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } return response; @@ -359,7 +359,7 @@ namespace Bit.App.Services else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden || response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } return response; diff --git a/src/App/Services/FolderService.cs b/src/App/Services/FolderService.cs index 10e5eb162..7a7c73666 100644 --- a/src/App/Services/FolderService.cs +++ b/src/App/Services/FolderService.cs @@ -75,7 +75,7 @@ namespace Bit.App.Services else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden || response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } return response; @@ -91,7 +91,7 @@ namespace Bit.App.Services else if(response.StatusCode == System.Net.HttpStatusCode.Forbidden || response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } return response; diff --git a/src/App/Services/LockService.cs b/src/App/Services/LockService.cs index 0d2a33f08..967e8b268 100644 --- a/src/App/Services/LockService.cs +++ b/src/App/Services/LockService.cs @@ -4,6 +4,10 @@ using Plugin.Settings.Abstractions; using Plugin.Fingerprint.Abstractions; using Bit.App.Enums; using System.Threading.Tasks; +using Bit.App.Controls; +using Bit.App.Pages; +using Xamarin.Forms; +using System.Linq; namespace Bit.App.Services { @@ -79,5 +83,58 @@ namespace Bit.App.Services return LockType.Password; } } + + public async Task CheckLockAsync(bool forceLock) + { + if(TopPageIsLock()) + { + // already locked + return; + } + + var lockType = await GetLockTypeAsync(forceLock); + if(lockType == LockType.None) + { + return; + } + + _appSettings.Locked = true; + switch(lockType) + { + case LockType.Fingerprint: + await Application.Current.MainPage.Navigation.PushModalAsync( + new ExtendedNavigationPage(new LockFingerprintPage(!forceLock)), false); + break; + case LockType.PIN: + await Application.Current.MainPage.Navigation.PushModalAsync( + new ExtendedNavigationPage(new LockPinPage()), false); + break; + case LockType.Password: + await Application.Current.MainPage.Navigation.PushModalAsync( + new ExtendedNavigationPage(new LockPasswordPage()), false); + break; + default: + break; + } + } + + public bool TopPageIsLock() + { + var currentPage = Application.Current.MainPage.Navigation.ModalStack.LastOrDefault() as ExtendedNavigationPage; + if((currentPage?.CurrentPage as LockFingerprintPage) != null) + { + return true; + } + if((currentPage?.CurrentPage as LockPinPage) != null) + { + return true; + } + if((currentPage?.CurrentPage as LockPasswordPage) != null) + { + return true; + } + + return false; + } } } diff --git a/src/App/Services/SyncService.cs b/src/App/Services/SyncService.cs index d6d78f4f3..9130ad4e2 100644 --- a/src/App/Services/SyncService.cs +++ b/src/App/Services/SyncService.cs @@ -305,7 +305,7 @@ namespace Bit.App.Services if(Application.Current != null && (accountRevisionDate.StatusCode == System.Net.HttpStatusCode.Forbidden || accountRevisionDate.StatusCode == System.Net.HttpStatusCode.Unauthorized)) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } return false; @@ -477,7 +477,7 @@ namespace Bit.App.Services } SyncInProgress = true; - MessagingCenter.Send(Application.Current, "SyncStarted"); + MessagingCenter.Send(this, "SyncStarted"); } private void SyncCompleted(bool successfully) @@ -488,7 +488,7 @@ namespace Bit.App.Services } SyncInProgress = false; - MessagingCenter.Send(Application.Current, "SyncCompleted", successfully); + MessagingCenter.Send(this, "SyncCompleted", successfully); } private bool CheckSuccess(ApiResult result, bool logout = false) @@ -501,7 +501,7 @@ namespace Bit.App.Services result.StatusCode == System.Net.HttpStatusCode.Forbidden || result.StatusCode == System.Net.HttpStatusCode.Unauthorized)) { - MessagingCenter.Send(Application.Current, "Logout", (string)null); + _authService.LogOut(); } return false; diff --git a/src/iOS.Core/Services/GoogleAnalyticsService.cs b/src/iOS.Core/Services/GoogleAnalyticsService.cs index 874a2661a..9bd61d02b 100644 --- a/src/iOS.Core/Services/GoogleAnalyticsService.cs +++ b/src/iOS.Core/Services/GoogleAnalyticsService.cs @@ -8,15 +8,11 @@ namespace Bit.iOS.Core.Services public class GoogleAnalyticsService : IGoogleAnalyticsService { private readonly ITracker _tracker; - private readonly IAuthService _authService; public GoogleAnalyticsService( IAppIdService appIdService, - IAuthService authService, ISettings settings) { - _authService = authService; - Gai.SharedInstance.DispatchInterval = 10; Gai.SharedInstance.TrackUncaughtExceptions = false; _tracker = Gai.SharedInstance.GetTracker("UA-81915606-1"); diff --git a/src/iOS/Services/DeviceActionService.cs b/src/iOS/Services/DeviceActionService.cs index 68c6e8a45..9109cbf80 100644 --- a/src/iOS/Services/DeviceActionService.cs +++ b/src/iOS/Services/DeviceActionService.cs @@ -9,16 +9,21 @@ using Xamarin.Forms; using Photos; using System.Net; using System.Threading.Tasks; +using Bit.App.Models.Page; namespace Bit.iOS.Services { public class DeviceActionService : IDeviceActionService { private readonly IAppSettingsService _appSettingsService; + private readonly IDeviceInfoService _deviceInfoService; - public DeviceActionService(IAppSettingsService appSettingsService) + public DeviceActionService( + IAppSettingsService appSettingsService, + IDeviceInfoService deviceInfoService) { _appSettingsService = appSettingsService; + _deviceInfoService = deviceInfoService; } public void CopyToClipboard(string text) @@ -202,5 +207,48 @@ namespace Bit.iOS.Services MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileResult", new Tuple(data, fileName)); } + + public void Autofill(VaultListPageModel.Cipher cipher) + { + throw new NotImplementedException(); + } + + public void CloseAutofill() + { + throw new NotImplementedException(); + } + + public void Background() + { + // do nothing + } + + public void RateApp() + { + if(_deviceInfoService.Version < 11) + { + Device.OpenUri(new Uri("itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews" + + "?id=1137397744&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software")); + } + else + { + Device.OpenUri(new Uri("itms-apps://itunes.apple.com/us/app/id1137397744?action=write-review")); + } + } + + public void DismissKeyboard() + { + // do nothing + } + + public void OpenAccessibilitySettings() + { + throw new NotImplementedException(); + } + + public void LaunchApp(string appName) + { + throw new NotImplementedException(); + } } }