mirror of
https://github.com/bitwarden/mobile
synced 2025-12-15 15:53:44 +00:00
Merge branch 'master' into feature/totp-tab
This commit is contained in:
@@ -12,7 +12,7 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
|
|||||||
|
|
||||||
# Build/Run
|
# Build/Run
|
||||||
|
|
||||||
Please refer to the [Mobile section](https://contributing.bitwarden.com/clients/mobile) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
|
Please refer to the [Mobile section](https://contributing.bitwarden.com/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
|
||||||
|
|
||||||
# We're Hiring!
|
# We're Hiring!
|
||||||
|
|
||||||
|
|||||||
@@ -99,12 +99,13 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
|
ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
|
||||||
#if FDROID
|
#if FDROID
|
||||||
ServiceContainer.Register<ILogger>("logger", new StubLogger());
|
var logger = new StubLogger();
|
||||||
#elif DEBUG
|
#elif DEBUG
|
||||||
ServiceContainer.Register<ILogger>("logger", DebugLogger.Instance);
|
var logger = DebugLogger.Instance;
|
||||||
#else
|
#else
|
||||||
ServiceContainer.Register<ILogger>("logger", Logger.Instance);
|
var logger = Logger.Instance;
|
||||||
#endif
|
#endif
|
||||||
|
ServiceContainer.Register("logger", logger);
|
||||||
|
|
||||||
// Note: This might cause a race condition. Investigate more.
|
// Note: This might cause a race condition. Investigate more.
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
@@ -124,7 +125,7 @@ namespace Bit.Droid
|
|||||||
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
||||||
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
||||||
var localizeService = new LocalizeService();
|
var localizeService = new LocalizeService();
|
||||||
var broadcasterService = new BroadcasterService();
|
var broadcasterService = new BroadcasterService(logger);
|
||||||
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
||||||
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
||||||
var secureStorageService = new SecureStorageService();
|
var secureStorageService = new SecureStorageService();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.05.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.6.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||||
|
|
||||||
|
|||||||
@@ -28,17 +28,25 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||||
{
|
{
|
||||||
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
|
try
|
||||||
if ((int)Build.VERSION.SdkInt < 33)
|
|
||||||
{
|
{
|
||||||
await Clipboard.SetTextAsync(text);
|
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
|
||||||
}
|
if ((int)Build.VERSION.SdkInt < 33)
|
||||||
else
|
{
|
||||||
{
|
await Clipboard.SetTextAsync(text);
|
||||||
CopyToClipboard(text, isSensitive);
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
CopyToClipboard(text, isSensitive);
|
||||||
|
}
|
||||||
|
|
||||||
await ClearClipboardAlarmAsync(expiresInMs);
|
await ClearClipboardAlarmAsync(expiresInMs);
|
||||||
|
}
|
||||||
|
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
|
||||||
|
{
|
||||||
|
// #1962 Just ignore, the content is copied either way but there is some app interfiering in the process
|
||||||
|
// that the OS catches and just throws this exception.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsCopyNotificationHandledByPlatform()
|
public bool IsCopyNotificationHandledByPlatform()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Bit.App.Utilities.AccountManagement;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.Xaml;
|
using Xamarin.Forms.Xaml;
|
||||||
@@ -56,86 +57,93 @@ namespace Bit.App
|
|||||||
Bootstrap();
|
Bootstrap();
|
||||||
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "showDialog")
|
try
|
||||||
{
|
{
|
||||||
var details = message.Data as DialogDetails;
|
if (message.Command == "showDialog")
|
||||||
var confirmed = true;
|
|
||||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
|
||||||
AppResources.Ok : details.ConfirmText;
|
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
var details = message.Data as DialogDetails;
|
||||||
|
var confirmed = true;
|
||||||
|
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
||||||
|
AppResources.Ok : details.ConfirmText;
|
||||||
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
||||||
details.CancelText);
|
{
|
||||||
}
|
confirmed = await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
||||||
else
|
details.CancelText);
|
||||||
{
|
}
|
||||||
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
|
else
|
||||||
}
|
{
|
||||||
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
await Current.MainPage.DisplayAlert(details.Title, details.Text, confirmText);
|
||||||
});
|
}
|
||||||
}
|
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||||
else if (message.Command == "resumed")
|
});
|
||||||
{
|
}
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
else if (message.Command == "resumed")
|
||||||
{
|
{
|
||||||
ResumedAsync().FireAndForget();
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
ResumedAsync().FireAndForget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == "slept")
|
||||||
|
{
|
||||||
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
|
{
|
||||||
|
await SleptAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == "migrated")
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||||
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
||||||
|
message.Command == "popAllAndGoToTabMyVault" ||
|
||||||
|
message.Command == "popAllAndGoToTabSend" ||
|
||||||
|
message.Command == "popAllAndGoToAutofillCiphers")
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
if (Current.MainPage is TabsPage tabsPage)
|
||||||
|
{
|
||||||
|
while (tabsPage.Navigation.ModalStack.Count > 0)
|
||||||
|
{
|
||||||
|
await tabsPage.Navigation.PopModalAsync(false);
|
||||||
|
}
|
||||||
|
if (message.Command == "popAllAndGoToAutofillCiphers")
|
||||||
|
{
|
||||||
|
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
|
||||||
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabMyVault")
|
||||||
|
{
|
||||||
|
Options.MyVaultTile = false;
|
||||||
|
tabsPage.ResetToVaultPage();
|
||||||
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabGenerator")
|
||||||
|
{
|
||||||
|
Options.GeneratorTile = false;
|
||||||
|
tabsPage.ResetToGeneratorPage();
|
||||||
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabSend")
|
||||||
|
{
|
||||||
|
tabsPage.ResetToSendPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (message.Command == "convertAccountToKeyConnector")
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
await Application.Current.MainPage.Navigation.PushModalAsync(
|
||||||
|
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (message.Command == "slept")
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
{
|
|
||||||
await SleptAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (message.Command == "migrated")
|
|
||||||
{
|
|
||||||
await Task.Delay(1000);
|
|
||||||
await _accountsManager.NavigateOnAccountChangeAsync();
|
|
||||||
}
|
|
||||||
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
|
||||||
message.Command == "popAllAndGoToTabMyVault" ||
|
|
||||||
message.Command == "popAllAndGoToTabSend" ||
|
|
||||||
message.Command == "popAllAndGoToAutofillCiphers")
|
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
if (Current.MainPage is TabsPage tabsPage)
|
|
||||||
{
|
|
||||||
while (tabsPage.Navigation.ModalStack.Count > 0)
|
|
||||||
{
|
|
||||||
await tabsPage.Navigation.PopModalAsync(false);
|
|
||||||
}
|
|
||||||
if (message.Command == "popAllAndGoToAutofillCiphers")
|
|
||||||
{
|
|
||||||
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
|
|
||||||
}
|
|
||||||
else if (message.Command == "popAllAndGoToTabMyVault")
|
|
||||||
{
|
|
||||||
Options.MyVaultTile = false;
|
|
||||||
tabsPage.ResetToVaultPage();
|
|
||||||
}
|
|
||||||
else if (message.Command == "popAllAndGoToTabGenerator")
|
|
||||||
{
|
|
||||||
Options.GeneratorTile = false;
|
|
||||||
tabsPage.ResetToGeneratorPage();
|
|
||||||
}
|
|
||||||
else if (message.Command == "popAllAndGoToTabSend")
|
|
||||||
{
|
|
||||||
tabsPage.ResetToSendPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (message.Command == "convertAccountToKeyConnector")
|
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
await Application.Current.MainPage.Navigation.PushModalAsync(
|
|
||||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||||
}
|
}
|
||||||
_broadcasterService.Subscribe(nameof(HomePage), async (message) =>
|
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "updatedTheme")
|
if (message.Command == "updatedTheme")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ namespace Bit.App.Pages
|
|||||||
_vm = BindingContext as LockPageViewModel;
|
_vm = BindingContext as LockPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
|
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
|
||||||
MasterPasswordEntry = _masterPassword;
|
|
||||||
PinEntry = _pin;
|
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
@@ -38,8 +36,17 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entry MasterPasswordEntry { get; set; }
|
public Entry SecretEntry
|
||||||
public Entry PinEntry { get; set; }
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_vm?.PinLock ?? false)
|
||||||
|
{
|
||||||
|
return _pin;
|
||||||
|
}
|
||||||
|
return _masterPassword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task PromptBiometricAfterResumeAsync()
|
public async Task PromptBiometricAfterResumeAsync()
|
||||||
{
|
{
|
||||||
@@ -70,16 +77,12 @@ namespace Bit.App.Pages
|
|||||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
||||||
|
|
||||||
await _vm.InitAsync();
|
await _vm.InitAsync();
|
||||||
|
|
||||||
|
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
||||||
|
|
||||||
if (!_vm.BiometricLock)
|
if (!_vm.BiometricLock)
|
||||||
{
|
{
|
||||||
if (_vm.PinLock)
|
RequestFocus(SecretEntry);
|
||||||
{
|
|
||||||
RequestFocus(PinEntry);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RequestFocus(MasterPasswordEntry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -99,6 +102,18 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PerformFocusSecretEntry(int? cursorPosition)
|
||||||
|
{
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
|
{
|
||||||
|
SecretEntry.Focus();
|
||||||
|
if (cursorPosition.HasValue)
|
||||||
|
{
|
||||||
|
SecretEntry.CursorPosition = cursorPosition.Value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnBackButtonPressed()
|
protected override bool OnBackButtonPressed()
|
||||||
{
|
{
|
||||||
if (_accountListOverlay.IsVisible)
|
if (_accountListOverlay.IsVisible)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.Helpers;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -27,6 +28,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IBiometricService _biometricService;
|
private readonly IBiometricService _biometricService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
||||||
|
|
||||||
private string _email;
|
private string _email;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
@@ -133,6 +135,11 @@ namespace Bit.App.Pages
|
|||||||
public string MasterPassword { get; set; }
|
public string MasterPassword { get; set; }
|
||||||
public string Pin { get; set; }
|
public string Pin { get; set; }
|
||||||
public Action UnlockedAction { get; set; }
|
public Action UnlockedAction { get; set; }
|
||||||
|
public event Action<int?> FocusSecretEntry
|
||||||
|
{
|
||||||
|
add => _secretEntryFocusWeakEventManager.AddEventHandler(value);
|
||||||
|
remove => _secretEntryFocusWeakEventManager.RemoveEventHandler(value);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
@@ -346,11 +353,8 @@ namespace Bit.App.Pages
|
|||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
ShowPassword = !ShowPassword;
|
ShowPassword = !ShowPassword;
|
||||||
var page = (Page as LockPage);
|
var secret = PinLock ? Pin : MasterPassword;
|
||||||
var entry = PinLock ? page.PinEntry : page.MasterPasswordEntry;
|
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
||||||
var str = PinLock ? Pin : MasterPassword;
|
|
||||||
entry.Focus();
|
|
||||||
entry.CursorPosition = String.IsNullOrEmpty(str) ? 0 : str.Length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
@@ -361,18 +365,8 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
PinLock ? AppResources.PIN : AppResources.MasterPassword, () =>
|
PinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||||
{
|
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
||||||
var page = Page as LockPage;
|
|
||||||
if (PinLock)
|
|
||||||
{
|
|
||||||
page.PinEntry.Focus();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
page.MasterPasswordEntry.Focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await _stateService.SetBiometricLockedAsync(!success);
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -81,10 +81,12 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||||
|
string ssoToken;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _apiService.PreValidateSso(OrgIdentifier);
|
var response = await _apiService.PreValidateSso(OrgIdentifier);
|
||||||
|
ssoToken = response.Token;
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
@@ -112,7 +114,8 @@ namespace Bit.App.Pages
|
|||||||
"response_type=code&scope=api%20offline_access&" +
|
"response_type=code&scope=api%20offline_access&" +
|
||||||
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
"state=" + state + "&code_challenge=" + codeChallenge + "&" +
|
||||||
"code_challenge_method=S256&response_mode=query&" +
|
"code_challenge_method=S256&response_mode=query&" +
|
||||||
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier);
|
"domain_hint=" + Uri.EscapeDataString(OrgIdentifier) + "&" +
|
||||||
|
"ssoToken=" + Uri.EscapeDataString(ssoToken);
|
||||||
|
|
||||||
WebAuthenticatorResult authResult = null;
|
WebAuthenticatorResult authResult = null;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -219,7 +219,8 @@ namespace Bit.App.Pages
|
|||||||
// Request
|
// Request
|
||||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
ResetPasswordKey = encryptedKey.EncryptedString
|
ResetPasswordKey = encryptedKey.EncryptedString,
|
||||||
|
MasterPasswordHash = masterPasswordHash,
|
||||||
};
|
};
|
||||||
var userId = await _stateService.GetActiveUserIdAsync();
|
var userId = await _stateService.GetActiveUserIdAsync();
|
||||||
// Enroll user
|
// Enroll user
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Bit.App.Controls;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
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;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -68,21 +69,28 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(_pageName, async (message) =>
|
_broadcasterService.Subscribe(_pageName, async (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "syncStarted")
|
try
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
if (message.Command == "syncStarted")
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted" || message.Command == "sendUpdated")
|
|
||||||
{
|
|
||||||
await Task.Delay(500);
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
{
|
||||||
IsBusy = false;
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||||
if (_vm.LoadedOnce)
|
}
|
||||||
|
else if (message.Command == "syncCompleted" || message.Command == "sendUpdated")
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
var task = _vm.LoadAsync();
|
IsBusy = false;
|
||||||
}
|
if (_vm.LoadedOnce)
|
||||||
});
|
{
|
||||||
|
var task = _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Bit.App.Models;
|
|||||||
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.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -55,21 +56,28 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) =>
|
_broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "syncStarted")
|
try
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
if (message.Command == "syncStarted")
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted")
|
|
||||||
{
|
|
||||||
await Task.Delay(500);
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
{
|
||||||
IsBusy = false;
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||||
if (_vm.LoadedOnce)
|
}
|
||||||
|
else if (message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
var task = _vm.LoadAsync();
|
IsBusy = false;
|
||||||
}
|
if (_vm.LoadedOnce)
|
||||||
});
|
{
|
||||||
|
var task = _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Bit.App.Resources;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -95,21 +96,28 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(_pageName, async (message) =>
|
_broadcasterService.Subscribe(_pageName, async (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "syncStarted")
|
try
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
if (message.Command == "syncStarted")
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted")
|
|
||||||
{
|
|
||||||
await Task.Delay(500);
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
{
|
||||||
IsBusy = false;
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||||
if (_vm.LoadedOnce)
|
}
|
||||||
|
else if (message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
var task = _vm.LoadAsync();
|
IsBusy = false;
|
||||||
}
|
if (_vm.LoadedOnce)
|
||||||
});
|
{
|
||||||
|
var task = _vm.LoadAsync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -56,37 +57,44 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(nameof(ViewPage), async (message) =>
|
_broadcasterService.Subscribe(nameof(ViewPage), async (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "syncStarted")
|
try
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
if (message.Command == "syncStarted")
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted")
|
|
||||||
{
|
|
||||||
await Task.Delay(500);
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
{
|
||||||
IsBusy = false;
|
Device.BeginInvokeOnMainThread(() => IsBusy = true);
|
||||||
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
|
}
|
||||||
|
else if (message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
var success = data["successfully"] as bool?;
|
IsBusy = false;
|
||||||
if (success.GetValueOrDefault())
|
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
|
||||||
{
|
{
|
||||||
var task = _vm.LoadAsync(() => AdjustToolbar());
|
var success = data["successfully"] as bool?;
|
||||||
|
if (success.GetValueOrDefault())
|
||||||
|
{
|
||||||
|
var task = _vm.LoadAsync(() => AdjustToolbar());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
else if (message.Command == "selectSaveFileResult")
|
||||||
else if (message.Command == "selectSaveFileResult")
|
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
{
|
||||||
var data = message.Data as Tuple<string, string>;
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
if (data == null)
|
|
||||||
{
|
{
|
||||||
return;
|
var data = message.Data as Tuple<string, string>;
|
||||||
}
|
if (data == null)
|
||||||
_vm.SaveFileSelected(data.Item1, data.Item2);
|
{
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
_vm.SaveFileSelected(data.Item1, data.Item2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await LoadOnAppearedAsync(_scrollView, true, async () =>
|
await LoadOnAppearedAsync(_scrollView, true, async () =>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
@@ -11,6 +12,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -28,6 +30,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IPasswordRepromptService _passwordRepromptService;
|
private readonly IPasswordRepromptService _passwordRepromptService;
|
||||||
private readonly ILocalizeService _localizeService;
|
private readonly ILocalizeService _localizeService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private CipherView _cipher;
|
private CipherView _cipher;
|
||||||
private List<ViewPageFieldViewModel> _fields;
|
private List<ViewPageFieldViewModel> _fields;
|
||||||
@@ -58,10 +61,11 @@ namespace Bit.App.Pages
|
|||||||
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
|
||||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
CopyCommand = new Command<string>((id) => CopyAsync(id, null));
|
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||||
CopyUriCommand = new Command<LoginUriView>(CopyUri);
|
CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||||
CopyFieldCommand = new Command<FieldView>(CopyField);
|
CopyFieldCommand = new AsyncCommand<FieldView>(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
|
||||||
LaunchUriCommand = new Command<LoginUriView>(LaunchUri);
|
LaunchUriCommand = new Command<LoginUriView>(LaunchUri);
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
||||||
@@ -72,9 +76,9 @@ namespace Bit.App.Pages
|
|||||||
PageTitle = AppResources.ViewItem;
|
PageTitle = AppResources.ViewItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Command CopyCommand { get; set; }
|
public ICommand CopyCommand { get; set; }
|
||||||
public Command CopyUriCommand { get; set; }
|
public ICommand CopyUriCommand { get; set; }
|
||||||
public Command CopyFieldCommand { get; set; }
|
public ICommand CopyFieldCommand { get; set; }
|
||||||
public Command LaunchUriCommand { get; set; }
|
public Command LaunchUriCommand { get; set; }
|
||||||
public Command TogglePasswordCommand { get; set; }
|
public Command TogglePasswordCommand { get; set; }
|
||||||
public Command ToggleCardNumberCommand { get; set; }
|
public Command ToggleCardNumberCommand { get; set; }
|
||||||
@@ -623,7 +627,7 @@ namespace Bit.App.Pages
|
|||||||
_attachmentFilename = null;
|
_attachmentFilename = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void CopyAsync(string id, string text = null)
|
private async Task CopyAsync(string id, string text = null)
|
||||||
{
|
{
|
||||||
if (_passwordRepromptService.ProtectedFields.Contains(id) && !await PromptPasswordAsync())
|
if (_passwordRepromptService.ProtectedFields.Contains(id) && !await PromptPasswordAsync())
|
||||||
{
|
{
|
||||||
@@ -687,16 +691,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyUri(LoginUriView uri)
|
|
||||||
{
|
|
||||||
CopyAsync("LoginUri", uri.Uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CopyField(FieldView field)
|
|
||||||
{
|
|
||||||
CopyAsync(field.Type == Core.Enums.FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LaunchUri(LoginUriView uri)
|
private void LaunchUri(LoginUriView uri)
|
||||||
{
|
{
|
||||||
if (uri.CanLaunch && (Page as BaseContentPage).DoOnce())
|
if (uri.CanLaunch && (Page as BaseContentPage).DoOnce())
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task PutDeleteCipherAsync(string id);
|
Task PutDeleteCipherAsync(string id);
|
||||||
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
Task<CipherResponse> PutRestoreCipherAsync(string id);
|
||||||
Task RefreshIdentityTokenAsync();
|
Task RefreshIdentityTokenAsync();
|
||||||
Task<object> PreValidateSso(string identifier);
|
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
|
||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrls urls);
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
public interface IBroadcasterService
|
public interface IBroadcasterService
|
||||||
{
|
{
|
||||||
void Send(Message message, string id = null);
|
void Send(Message message);
|
||||||
|
void Send(Message message, string id);
|
||||||
void Subscribe(string id, Action<Message> messageCallback);
|
void Subscribe(string id, Action<Message> messageCallback);
|
||||||
void Unsubscribe(string id);
|
void Unsubscribe(string id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class OrganizationUserResetPasswordEnrollmentRequest
|
public class OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
|
public string MasterPasswordHash { get; set; }
|
||||||
public string ResetPasswordKey { get; set; }
|
public string ResetPasswordKey { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/Core/Models/Response/SsoPrevalidateResponse.cs
Normal file
7
src/Core/Models/Response/SsoPrevalidateResponse.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Bit.Core.Models.Response
|
||||||
|
{
|
||||||
|
public class SsoPrevalidateResponse
|
||||||
|
{
|
||||||
|
public string Token { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -547,7 +547,7 @@ namespace Bit.Core.Services
|
|||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<object> PreValidateSso(string identifier)
|
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
|
||||||
{
|
{
|
||||||
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
|
||||||
using (var requestMessage = new HttpRequestMessage())
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
@@ -571,7 +571,8 @@ namespace Bit.Core.Services
|
|||||||
var error = await HandleErrorAsync(response, false, true);
|
var error = await HandleErrorAsync(response, false, true);
|
||||||
throw new ApiException(error);
|
throw new ApiException(error);
|
||||||
}
|
}
|
||||||
return null;
|
var responseJsonString = await response.Content.ReadAsStringAsync();
|
||||||
|
return JsonConvert.DeserializeObject<SsoPrevalidateResponse>(responseJsonString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,24 +8,58 @@ namespace Bit.App.Services
|
|||||||
{
|
{
|
||||||
public class BroadcasterService : IBroadcasterService
|
public class BroadcasterService : IBroadcasterService
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly Dictionary<string, Action<Message>> _subscribers = new Dictionary<string, Action<Message>>();
|
private readonly Dictionary<string, Action<Message>> _subscribers = new Dictionary<string, Action<Message>>();
|
||||||
private object _myLock = new object();
|
private object _myLock = new object();
|
||||||
|
|
||||||
public void Send(Message message, string id = null)
|
public BroadcasterService(ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(Message message)
|
||||||
{
|
{
|
||||||
lock (_myLock)
|
lock (_myLock)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(id))
|
|
||||||
{
|
|
||||||
if (_subscribers.ContainsKey(id))
|
|
||||||
{
|
|
||||||
Task.Run(() => _subscribers[id].Invoke(message));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach (var sub in _subscribers)
|
foreach (var sub in _subscribers)
|
||||||
{
|
{
|
||||||
Task.Run(() => sub.Value.Invoke(message));
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sub.Value(message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(Message message, string id)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_myLock)
|
||||||
|
{
|
||||||
|
if (_subscribers.TryGetValue(id, out var action))
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action(message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,14 +68,7 @@ namespace Bit.App.Services
|
|||||||
{
|
{
|
||||||
lock (_myLock)
|
lock (_myLock)
|
||||||
{
|
{
|
||||||
if (_subscribers.ContainsKey(id))
|
_subscribers[id] = messageCallback;
|
||||||
{
|
|
||||||
_subscribers[id] = messageCallback;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_subscribers.Add(id, messageCallback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@@ -8,7 +10,7 @@ namespace Bit.Core.Utilities
|
|||||||
{
|
{
|
||||||
public static class ServiceContainer
|
public static class ServiceContainer
|
||||||
{
|
{
|
||||||
public static Dictionary<string, object> RegisteredServices { get; set; } = new Dictionary<string, object>();
|
public static ConcurrentDictionary<string, object> RegisteredServices { get; set; } = new ConcurrentDictionary<string, object>();
|
||||||
public static bool Inited { get; set; }
|
public static bool Inited { get; set; }
|
||||||
|
|
||||||
public static void Init(string customUserAgent = null, string clearCipherCacheKey = null,
|
public static void Init(string customUserAgent = null, string clearCipherCacheKey = null,
|
||||||
@@ -109,18 +111,17 @@ namespace Bit.Core.Utilities
|
|||||||
|
|
||||||
public static void Register<T>(string serviceName, T obj)
|
public static void Register<T>(string serviceName, T obj)
|
||||||
{
|
{
|
||||||
if (RegisteredServices.ContainsKey(serviceName))
|
if (!RegisteredServices.TryAdd(serviceName, obj))
|
||||||
{
|
{
|
||||||
throw new Exception($"Service {serviceName} has already been registered.");
|
throw new Exception($"Service {serviceName} has already been registered.");
|
||||||
}
|
}
|
||||||
RegisteredServices.Add(serviceName, obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T Resolve<T>(string serviceName, bool dontThrow = false)
|
public static T Resolve<T>(string serviceName, bool dontThrow = false)
|
||||||
{
|
{
|
||||||
if (RegisteredServices.ContainsKey(serviceName))
|
if (RegisteredServices.TryGetValue(serviceName, out var service))
|
||||||
{
|
{
|
||||||
return (T)RegisteredServices[serviceName];
|
return (T)service;
|
||||||
}
|
}
|
||||||
if (dontThrow)
|
if (dontThrow)
|
||||||
{
|
{
|
||||||
@@ -129,6 +130,59 @@ namespace Bit.Core.Utilities
|
|||||||
throw new Exception($"Service {serviceName} is not registered.");
|
throw new Exception($"Service {serviceName} is not registered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Register<T>(T obj)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
Register(typeof(T), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Register(Type type, object obj)
|
||||||
|
{
|
||||||
|
var serviceName = GetServiceRegistrationName(type);
|
||||||
|
if (!RegisteredServices.TryAdd(serviceName, obj))
|
||||||
|
{
|
||||||
|
throw new Exception($"Service {serviceName} has already been registered.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T Resolve<T>()
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
return (T)Resolve(typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object Resolve(Type type)
|
||||||
|
{
|
||||||
|
var serviceName = GetServiceRegistrationName(type);
|
||||||
|
if (RegisteredServices.TryGetValue(serviceName, out var service))
|
||||||
|
{
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
throw new Exception($"Service {serviceName} is not registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryResolve<T>(out T service)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var toReturn = TryResolve(typeof(T), out var serviceObj);
|
||||||
|
service = (T)serviceObj;
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
service = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryResolve(Type type, out object service)
|
||||||
|
{
|
||||||
|
var serviceName = GetServiceRegistrationName(type);
|
||||||
|
return RegisteredServices.TryGetValue(serviceName, out service);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Reset()
|
public static void Reset()
|
||||||
{
|
{
|
||||||
foreach (var service in RegisteredServices)
|
foreach (var service in RegisteredServices)
|
||||||
@@ -140,7 +194,33 @@ namespace Bit.Core.Utilities
|
|||||||
}
|
}
|
||||||
Inited = false;
|
Inited = false;
|
||||||
RegisteredServices.Clear();
|
RegisteredServices.Clear();
|
||||||
RegisteredServices = new Dictionary<string, object>();
|
RegisteredServices = new ConcurrentDictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the service registration name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type of the service</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// In order to work with already register/resolve we need to maintain the naming convention
|
||||||
|
/// of camelCase without the first "I" on the services interfaces
|
||||||
|
/// e.g. "ITokenService" -> "tokenService"
|
||||||
|
/// </remarks>
|
||||||
|
static string GetServiceRegistrationName(Type type)
|
||||||
|
{
|
||||||
|
var typeName = type.Name;
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
var indexToLowerCase = 0;
|
||||||
|
if (typeName[0] == 'I' && char.IsUpper(typeName[1]))
|
||||||
|
{
|
||||||
|
// if it's an interface then we ignore the first char
|
||||||
|
// and lower case the 2nd one (index 1)
|
||||||
|
indexToLowerCase = 1;
|
||||||
|
}
|
||||||
|
sb.Append(char.ToLower(typeName[indexToLowerCase]));
|
||||||
|
sb.Append(typeName.Substring(++indexToLowerCase));
|
||||||
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.05.1</string>
|
<string>2022.6.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Bit.iOS.Core.Renderers
|
|||||||
public CustomTabbedRenderer()
|
public CustomTabbedRenderer()
|
||||||
{
|
{
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_broadcasterService.Subscribe(nameof(CustomTabbedRenderer), async (message) =>
|
_broadcasterService.Subscribe(nameof(CustomTabbedRenderer), (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "updatedTheme")
|
if (message.Command == "updatedTheme")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,13 +38,15 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
|
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ILogger logger = null;
|
||||||
if (ServiceContainer.Resolve<ILogger>("logger", true) == null)
|
if (ServiceContainer.Resolve<ILogger>("logger", true) == null)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
ServiceContainer.Register<ILogger>("logger", DebugLogger.Instance);
|
logger = DebugLogger.Instance;
|
||||||
#else
|
#else
|
||||||
ServiceContainer.Register<ILogger>("logger", Logger.Instance);
|
logger = Logger.Instance;
|
||||||
#endif
|
#endif
|
||||||
|
ServiceContainer.Register("logger", logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
var preferencesStorage = new PreferencesStorageService(AppGroupId);
|
var preferencesStorage = new PreferencesStorageService(AppGroupId);
|
||||||
@@ -52,7 +54,7 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
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();
|
||||||
var broadcasterService = new BroadcasterService();
|
var broadcasterService = new BroadcasterService(logger);
|
||||||
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
||||||
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
||||||
var secureStorageService = new KeyChainStorageService(AppId, AccessGroup,
|
var secureStorageService = new KeyChainStorageService(AppId, AccessGroup,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.05.1</string>
|
<string>2022.6.1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.05.1</string>
|
<string>2022.6.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -59,114 +59,121 @@ namespace Bit.iOS
|
|||||||
|
|
||||||
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
|
_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 == "updatedTheme")
|
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
{
|
||||||
iOSCoreHelpers.AppearanceAdjustments();
|
StartEventTimer();
|
||||||
});
|
}
|
||||||
}
|
else if (message.Command == "stopEventTimer")
|
||||||
else if (message.Command == "listenYubiKeyOTP")
|
{
|
||||||
{
|
var task = StopEventTimerAsync();
|
||||||
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
|
}
|
||||||
}
|
else if (message.Command == "updatedTheme")
|
||||||
else if (message.Command == "unlocked")
|
{
|
||||||
{
|
Device.BeginInvokeOnMainThread(() =>
|
||||||
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
|
{
|
||||||
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
iOSCoreHelpers.AppearanceAdjustments();
|
||||||
if (needsAutofillReplacement.GetValueOrDefault())
|
});
|
||||||
|
}
|
||||||
|
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 == "showAppExtension")
|
||||||
|
{
|
||||||
|
Device.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?;
|
||||||
|
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
|
||||||
|
{
|
||||||
|
await ASHelpers.ReplaceAllIdentities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
|
||||||
|
message.Command == "restoredCipher")
|
||||||
|
{
|
||||||
|
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||||
|
{
|
||||||
|
if (await ASHelpers.IdentitiesCanIncremental())
|
||||||
|
{
|
||||||
|
var cipherId = message.Data as string;
|
||||||
|
if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId))
|
||||||
|
{
|
||||||
|
var identity = await ASHelpers.GetCipherIdentityAsync(cipherId);
|
||||||
|
if (identity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ASCredentialIdentityStore.SharedStore?.SaveCredentialIdentitiesAsync(
|
||||||
|
new ASPasswordCredentialIdentity[] { identity });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await ASHelpers.ReplaceAllIdentities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
|
||||||
|
{
|
||||||
|
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||||
|
{
|
||||||
|
if (await ASHelpers.IdentitiesCanIncremental())
|
||||||
|
{
|
||||||
|
var identity = ASHelpers.ToCredentialIdentity(
|
||||||
|
message.Data as Bit.Core.Models.View.CipherView);
|
||||||
|
if (identity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ASCredentialIdentityStore.SharedStore?.RemoveCredentialIdentitiesAsync(
|
||||||
|
new ASPasswordCredentialIdentity[] { identity });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ASHelpers.ReplaceAllIdentities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == "logout")
|
||||||
|
{
|
||||||
|
if (_deviceActionService.SystemMajorVersion() >= 12)
|
||||||
|
{
|
||||||
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
||||||
|
&& _deviceActionService.SystemMajorVersion() >= 12)
|
||||||
{
|
{
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
await ASHelpers.ReplaceAllIdentities();
|
||||||
}
|
}
|
||||||
}
|
else if (message.Command == "vaultTimeoutActionChanged")
|
||||||
else if (message.Command == "showAppExtension")
|
|
||||||
{
|
|
||||||
Device.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 timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
||||||
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
|
if (timeoutAction == VaultTimeoutAction.Logout)
|
||||||
|
{
|
||||||
|
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
await ASHelpers.ReplaceAllIdentities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
|
catch (Exception ex)
|
||||||
message.Command == "restoredCipher")
|
|
||||||
{
|
{
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
{
|
|
||||||
if (await ASHelpers.IdentitiesCanIncremental())
|
|
||||||
{
|
|
||||||
var cipherId = message.Data as string;
|
|
||||||
if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId))
|
|
||||||
{
|
|
||||||
var identity = await ASHelpers.GetCipherIdentityAsync(cipherId);
|
|
||||||
if (identity == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await ASCredentialIdentityStore.SharedStore?.SaveCredentialIdentitiesAsync(
|
|
||||||
new ASPasswordCredentialIdentity[] { identity });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
|
|
||||||
{
|
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
|
||||||
{
|
|
||||||
if (await ASHelpers.IdentitiesCanIncremental())
|
|
||||||
{
|
|
||||||
var identity = ASHelpers.ToCredentialIdentity(
|
|
||||||
message.Data as Bit.Core.Models.View.CipherView);
|
|
||||||
if (identity == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveCredentialIdentitiesAsync(
|
|
||||||
new ASPasswordCredentialIdentity[] { identity });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (message.Command == "logout")
|
|
||||||
{
|
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
|
||||||
{
|
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
|
||||||
&& _deviceActionService.SystemMajorVersion() >= 12)
|
|
||||||
{
|
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
|
||||||
}
|
|
||||||
else if (message.Command == "vaultTimeoutActionChanged")
|
|
||||||
{
|
|
||||||
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
|
||||||
if (timeoutAction == VaultTimeoutAction.Logout)
|
|
||||||
{
|
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden</string>
|
<string>com.8bit.bitwarden</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.05.1</string>
|
<string>2022.6.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user