1
0
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:
André Bispo
2022-07-07 15:33:08 +01:00
29 changed files with 512 additions and 330 deletions

View File

@@ -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!

View File

@@ -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();

View File

@@ -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"/>

View File

@@ -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()

View File

@@ -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()));
});
} }
}); });
} }

View File

@@ -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")
{ {

View File

@@ -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)

View File

@@ -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)
{ {

View File

@@ -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

View File

@@ -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

View File

@@ -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);
} }
}); });

View File

@@ -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);
} }
}); });

View File

@@ -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);
} }
}); });

View File

@@ -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 () =>

View File

@@ -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())

View File

@@ -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);

View File

@@ -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);
} }

View File

@@ -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; }
} }
} }

View File

@@ -0,0 +1,7 @@
namespace Bit.Core.Models.Response
{
public class SsoPrevalidateResponse
{
public string Token { get; set; }
}
}

View File

@@ -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);
} }
} }

View File

@@ -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);
}
} }
} }

View File

@@ -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();
} }
} }
} }

View File

@@ -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>

View File

@@ -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")
{ {

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
}
} }
}); });

View File

@@ -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>