1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-17 00:33:20 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
André Bispo
2dd6a3f294 [PM-3340] Change settings screen vault timeout action cell UI. 2023-09-13 17:28:03 +01:00
21 changed files with 7678 additions and 7028 deletions

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="2023.9.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="2023.8.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" /> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />

View File

@@ -7,7 +7,6 @@ using Bit.App.Utilities;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Services; using Bit.Core.Services;
@@ -73,8 +72,7 @@ namespace Bit.App.Pages
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
SubmitCommand = new Command(async () => await SubmitAsync()); SubmitCommand = new Command(async () => await SubmitAsync());
AccountSwitchingOverlayViewModel = AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{ {
AllowAddAccountRow = true, AllowAddAccountRow = true,
AllowActiveAccountSelection = true AllowActiveAccountSelection = true
@@ -157,12 +155,8 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; } public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
? AppResources.PasswordIsVisibleTapToHide
: AppResources.PasswordIsNotVisibleTapToShow;
public Action UnlockedAction { get; set; } public Action UnlockedAction { get; set; }
public event Action<int?> FocusSecretEntry public event Action<int?> FocusSecretEntry
{ {
@@ -185,8 +179,7 @@ namespace Bit.App.Pages
?? await _stateService.GetPinProtectedKeyAsync(); ?? await _stateService.GetPinProtectedKeyAsync();
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) || PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent; _pinStatus == PinLockType.Persistent;
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _biometricService.CanUseBiometricsUnlockAsync();
BiometricEnabled = await IsBiometricsEnabledAsync();
// Users without MP and without biometric or pin has no MP to unlock with // Users without MP and without biometric or pin has no MP to unlock with
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync(); _hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
@@ -221,9 +214,7 @@ namespace Bit.App.Pages
else else
{ {
PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault; PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault;
LockedVerifyText = _hasMasterPassword LockedVerifyText = _hasMasterPassword ? AppResources.VaultLockedMasterPassword : AppResources.VaultLockedIdentity;
? AppResources.VaultLockedMasterPassword
: AppResources.VaultLockedIdentity;
} }
if (BiometricEnabled) if (BiometricEnabled)
@@ -242,32 +233,11 @@ namespace Bit.App.Pages
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock : BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
AppResources.UseFingerprintToUnlock; AppResources.UseFingerprintToUnlock;
} }
} }
} }
public async Task SubmitAsync() public async Task SubmitAsync()
{
ShowPassword = false;
try
{
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinEnabled)
{
await UnlockWithPinAsync(kdfConfig);
}
else
{
await UnlockWithMasterPasswordAsync(kdfConfig);
}
}
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
}
private async Task UnlockWithPinAsync(KdfConfig kdfConfig)
{ {
if (PinEnabled && string.IsNullOrWhiteSpace(Pin)) if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
{ {
@@ -276,28 +246,34 @@ namespace Bit.App.Pages
AppResources.Ok); AppResources.Ok);
return; return;
} }
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.Ok);
return;
}
ShowPassword = false;
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinEnabled)
{
var failed = true; var failed = true;
try try
{ {
EncString userKeyPin; EncString userKeyPin = null;
EncString oldPinProtected; EncString oldPinProtected = null;
switch (_pinStatus) if (_pinStatus == PinLockType.Persistent)
{
case PinLockType.Persistent:
{ {
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync(); userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync(); var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null; oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
break;
} }
case PinLockType.Transient: else if (_pinStatus == PinLockType.Transient)
{
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync(); userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync(); oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
break;
case PinLockType.Disabled:
default:
throw new Exception("Pin is disabled");
} }
UserKey userKey; UserKey userKey;
@@ -331,10 +307,6 @@ namespace Bit.App.Pages
await SetUserKeyAndContinueAsync(userKey); await SetUserKeyAndContinueAsync(userKey);
} }
} }
catch (LegacyUserException)
{
throw;
}
catch catch
{ {
failed = true; failed = true;
@@ -351,23 +323,9 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred); AppResources.AnErrorHasOccurred);
} }
} }
else
private async Task UnlockWithMasterPasswordAsync(KdfConfig kdfConfig)
{ {
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
AppResources.Ok);
return;
}
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
if (await _cryptoService.IsLegacyUserAsync(masterKey))
{
throw new LegacyUserException();
}
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync(); var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
var passwordValid = false; var passwordValid = false;
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null; MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
@@ -381,8 +339,7 @@ namespace Bit.App.Pages
{ {
// Online unlock required // Online unlock required
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest(); var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash; request.MasterPasswordHash = keyHash;
@@ -391,8 +348,7 @@ namespace Bit.App.Pages
var response = await _apiService.PostAccountVerifyPasswordAsync(request); var response = await _apiService.PostAccountVerifyPasswordAsync(request);
enforcedMasterPasswordOptions = response.MasterPasswordPolicy; enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
passwordValid = true; passwordValid = true;
var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
HashPurpose.LocalAuthorization);
await _cryptoService.SetMasterKeyHashAsync(localKeyHash); await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
} }
catch (Exception e) catch (Exception e)
@@ -401,7 +357,6 @@ namespace Bit.App.Pages
} }
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
} }
if (passwordValid) if (passwordValid)
{ {
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions)) if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
@@ -436,6 +391,7 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred); AppResources.AnErrorHasOccurred);
} }
} }
}
/// <summary> /// <summary>
/// Checks if the master password requires updating to meet the enforced policy requirements /// Checks if the master password requires updating to meet the enforced policy requirements
@@ -496,13 +452,10 @@ namespace Bit.App.Pages
{ {
ShowPassword = !ShowPassword; ShowPassword = !ShowPassword;
var secret = PinEnabled ? Pin : MasterPassword; var secret = PinEnabled ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, _secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
nameof(FocusSecretEntry));
} }
public async Task PromptBiometricAsync() public async Task PromptBiometricAsync()
{
try
{ {
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
BiometricButtonVisible = BiometricIntegrityValid; BiometricButtonVisible = BiometricIntegrityValid;
@@ -510,7 +463,6 @@ namespace Bit.App.Pages
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinEnabled ? AppResources.PIN : AppResources.MasterPassword, PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry))); () => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
@@ -521,11 +473,6 @@ namespace Bit.App.Pages
await SetUserKeyAndContinueAsync(userKey); await SetUserKeyAndContinueAsync(userKey);
} }
} }
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
}
private async Task SetUserKeyAndContinueAsync(UserKey key) private async Task SetUserKeyAndContinueAsync(UserKey key)
{ {
@@ -546,29 +493,5 @@ namespace Bit.App.Pages
_messagingService.Send("unlocked"); _messagingService.Send("unlocked");
UnlockedAction?.Invoke(); UnlockedAction?.Invoke();
} }
private async Task<bool> IsBiometricsEnabledAsync()
{
try
{
return await _vaultTimeoutService.IsBiometricLockSetAsync() &&
await _biometricService.CanUseBiometricsUnlockAsync();
}
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
return false;
}
private async Task HandleLegacyUserAsync()
{
// Legacy users must migrate on web vault.
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong,
AppResources.AnErrorHasOccurred,
AppResources.Ok);
await _vaultTimeoutService.LogOutAsync();
}
} }
} }

View File

@@ -248,14 +248,6 @@ namespace Bit.App.Pages
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
if (response.RequiresEncryptionKeyMigration)
{
// Legacy users must migrate on web vault.
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong, AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
if (response.TwoFactor) if (response.TwoFactor)
{ {
StartTwoFactorAction?.Invoke(); StartTwoFactorAction?.Invoke();

View File

@@ -49,6 +49,38 @@
AutomationId="{Binding AutomationIdSettingStatus}" /> AutomationId="{Binding AutomationIdSettingStatus}" />
</controls:ExtendedStackLayout> </controls:ExtendedStackLayout>
</DataTemplate> </DataTemplate>
<DataTemplate
x:Key="regularWithDescriptionTemplate"
x:DataType="pages:SettingsPageListItem">
<controls:ExtendedStackLayout Orientation="Horizontal"
StyleClass="list-row, list-row-platform">
<StackLayout
HorizontalOptions="StartAndExpand"
VerticalOptions="Center">
<controls:CustomLabel
Text="{Binding Name, Mode=OneWay}"
LineBreakMode="{Binding LineBreakMode}"
StyleClass="list-title"
TextColor="{Binding NameColor}"
AutomationId="{Binding AutomationIdSettingName}" />
<controls:CustomLabel
Text="{Binding Description, Mode=OneWay}"
LineBreakMode="{Binding LineBreakMode}"
TextColor="{DynamicResource MutedColor}"
FontSize="Micro"
StyleClass="list-sub"/>
</StackLayout>
<controls:CustomLabel Text="{Binding SubLabel, Mode=OneWay}"
IsVisible="{Binding ShowSubLabel}"
HorizontalOptions="End"
HorizontalTextAlignment="End"
VerticalOptions="CenterAndExpand"
VerticalTextAlignment="Start"
TextColor="{Binding SubLabelColor}"
StyleClass="list-sub"
AutomationId="{Binding AutomationIdSettingStatus}" />
</controls:ExtendedStackLayout>
</DataTemplate>
<DataTemplate <DataTemplate
x:Key="timePickerTemplate" x:Key="timePickerTemplate"
x:DataType="pages:SettingsPageListItem"> x:DataType="pages:SettingsPageListItem">
@@ -107,6 +139,7 @@
x:Key="listItemDataTemplateSelector" x:Key="listItemDataTemplateSelector"
HeaderTemplate="{StaticResource headerTemplate}" HeaderTemplate="{StaticResource headerTemplate}"
RegularTemplate="{StaticResource regularTemplate}" RegularTemplate="{StaticResource regularTemplate}"
RegularWithDescriptionTemplate="{StaticResource regularWithDescriptionTemplate}"
TimePickerTemplate="{StaticResource timePickerTemplate}" /> TimePickerTemplate="{StaticResource timePickerTemplate}" />
</ResourceDictionary> </ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>

View File

@@ -64,7 +64,7 @@ namespace Bit.App.Pages
private void RowSelected(object sender, SelectionChangedEventArgs e) private void RowSelected(object sender, SelectionChangedEventArgs e)
{ {
((ExtendedCollectionView)sender).SelectedItem = null; ((ExtendedCollectionView)sender).SelectedItem = null;
if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item) if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item && item.Enabled)
{ {
_vm?.ExecuteSettingItemCommand.Execute(item); _vm?.ExecuteSettingItemCommand.Execute(item);
} }

View File

@@ -13,8 +13,10 @@ namespace Bit.App.Pages
public string Icon { get; set; } public string Icon { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string SubLabel { get; set; } public string SubLabel { get; set; }
public string Description { get; set; }
public TimeSpan? Time { get; set; } public TimeSpan? Time { get; set; }
public bool UseFrame { get; set; } public bool UseFrame { get; set; }
public bool Enabled { get; set; } = true;
public Func<Task> ExecuteAsync { get; set; } public Func<Task> ExecuteAsync { get; set; }
public bool SubLabelTextEnabled => SubLabel == AppResources.On; public bool SubLabelTextEnabled => SubLabel == AppResources.On;
@@ -24,6 +26,9 @@ namespace Bit.App.Pages
public Color SubLabelColor => SubLabelTextEnabled ? public Color SubLabelColor => SubLabelTextEnabled ?
ThemeManager.GetResourceColor("SuccessColor") : ThemeManager.GetResourceColor("SuccessColor") :
ThemeManager.GetResourceColor("MutedColor"); ThemeManager.GetResourceColor("MutedColor");
public Color NameColor => Enabled ?
ThemeManager.GetResourceColor("TextColor") :
ThemeManager.GetResourceColor("MutedColor");
public string AutomationIdSettingName public string AutomationIdSettingName
{ {

View File

@@ -7,6 +7,7 @@ namespace Bit.App.Pages
public DataTemplate HeaderTemplate { get; set; } public DataTemplate HeaderTemplate { get; set; }
public DataTemplate RegularTemplate { get; set; } public DataTemplate RegularTemplate { get; set; }
public DataTemplate TimePickerTemplate { get; set; } public DataTemplate TimePickerTemplate { get; set; }
public DataTemplate RegularWithDescriptionTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container) protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{ {
@@ -16,6 +17,10 @@ namespace Bit.App.Pages
} }
if (item is SettingsPageListItem listItem) if (item is SettingsPageListItem listItem)
{ {
if (!string.IsNullOrEmpty(listItem.Description))
{
return RegularWithDescriptionTemplate;
}
return listItem.ShowTimeInput ? TimePickerTemplate : RegularTemplate; return listItem.ShowTimeInput ? TimePickerTemplate : RegularTemplate;
} }
return null; return null;

View File

@@ -567,6 +567,8 @@ namespace Bit.App.Pages
{ {
Name = AppResources.VaultTimeoutAction, Name = AppResources.VaultTimeoutAction,
SubLabel = _vaultTimeoutActionDisplayValue, SubLabel = _vaultTimeoutActionDisplayValue,
Description = IsVaultTimeoutActionLockAllowed ? null : AppResources.SetUpAnUnlockMethodToChangeYourVaultTimeoutAction,
Enabled = IsVaultTimeoutActionLockAllowed,
ExecuteAsync = () => VaultTimeoutActionAsync() ExecuteAsync = () => VaultTimeoutActionAsync()
}, },
new SettingsPageListItem new SettingsPageListItem
@@ -582,16 +584,19 @@ namespace Bit.App.Pages
ExecuteAsync = () => ApproveLoginRequestsAsync() ExecuteAsync = () => ApproveLoginRequestsAsync()
}, },
new SettingsPageListItem new SettingsPageListItem
{
Name = AppResources.LockNow,
ExecuteAsync = () => LockAsync()
},
new SettingsPageListItem
{ {
Name = AppResources.TwoStepLogin, Name = AppResources.TwoStepLogin,
ExecuteAsync = () => TwoStepAsync() ExecuteAsync = () => TwoStepAsync()
} }
}; };
if (IsVaultTimeoutActionLockAllowed)
{
securityItems.Insert(4, new SettingsPageListItem
{
Name = AppResources.LockNow,
ExecuteAsync = () => LockAsync()
});
}
if (_approvePasswordlessLoginRequests) if (_approvePasswordlessLoginRequests)
{ {
manageItems.Add(new SettingsPageListItem manageItems.Add(new SettingsPageListItem

File diff suppressed because it is too large Load Diff

View File

@@ -954,9 +954,6 @@ Scanning will happen automatically.</value>
<data name="UpdateKey" xml:space="preserve"> <data name="UpdateKey" xml:space="preserve">
<value>You cannot use this feature until you update your encryption key.</value> <value>You cannot use this feature until you update your encryption key.</value>
</data> </data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve"> <data name="LearnMore" xml:space="preserve">
<value>Learn more</value> <value>Learn more</value>
</data> </data>
@@ -2765,4 +2762,7 @@ Do you want to switch to this account?</value>
<data name="LoggingInOn" xml:space="preserve"> <data name="LoggingInOn" xml:space="preserve">
<value>Logging in on</value> <value>Logging in on</value>
</data> </data>
<data name="SetUpAnUnlockMethodToChangeYourVaultTimeoutAction" xml:space="preserve">
<value>Set up an unlock method to change your vault timeout action.</value>
</data>
</root> </root>

View File

@@ -13,7 +13,6 @@ namespace Bit.Core.Abstractions
Task RefreshKeysAsync(); Task RefreshKeysAsync();
Task SetUserKeyAsync(UserKey userKey, string userId = null); Task SetUserKeyAsync(UserKey userKey, string userId = null);
Task<UserKey> GetUserKeyAsync(string userId = null); Task<UserKey> GetUserKeyAsync(string userId = null);
Task<bool> IsLegacyUserAsync(MasterKey masterKey = null, string userId = null);
Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null); Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null);
Task<bool> HasUserKeyAsync(string userId = null); Task<bool> HasUserKeyAsync(string userId = null);
Task<bool> HasEncryptedUserKeyAsync(string userId = null); Task<bool> HasEncryptedUserKeyAsync(string userId = null);

View File

@@ -1,11 +0,0 @@
using System;
namespace Bit.Core.Exceptions
{
public class LegacyUserException : Exception
{
public LegacyUserException()
: base("Legacy users must migrate on web vault.")
{
}
}
}

View File

@@ -13,7 +13,6 @@ namespace Bit.Core.Models.Domain
[Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")] [Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")]
public bool ResetMasterPassword { get; set; } public bool ResetMasterPassword { get; set; }
public bool ForcePasswordReset { get; set; } public bool ForcePasswordReset { get; set; }
public bool RequiresEncryptionKeyMigration { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; } public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
} }
} }

View File

@@ -477,17 +477,6 @@ namespace Bit.Core.Services
} }
var tokenResponse = response.TokenResponse; var tokenResponse = response.TokenResponse;
if (localHashedPassword != null && tokenResponse.Key == null)
{
// Only check for legacy if there is no key on token
if (await _cryptoService.IsLegacyUserAsync(masterKey))
{
// Legacy users must migrate on web vault;
result.RequiresEncryptionKeyMigration = true;
return result;
}
}
result.ResetMasterPassword = tokenResponse.ResetMasterPassword; result.ResetMasterPassword = tokenResponse.ResetMasterPassword;
result.ForcePasswordReset = tokenResponse.ForcePasswordReset; result.ForcePasswordReset = tokenResponse.ForcePasswordReset;
_masterPasswordPolicy = tokenResponse.MasterPasswordPolicy; _masterPasswordPolicy = tokenResponse.MasterPasswordPolicy;

View File

@@ -62,16 +62,6 @@ namespace Bit.Core.Services
return _stateService.GetUserKeyAsync(userId); return _stateService.GetUserKeyAsync(userId);
} }
public async Task<bool> IsLegacyUserAsync(MasterKey masterKey = null, string userId = null)
{
masterKey ??= await GetMasterKeyAsync(userId);
if (masterKey == null)
{
return false;
}
return await ValidateUserKeyAsync(new UserKey(masterKey.Key));
}
public async Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null) public async Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null)
{ {
var userKey = await GetUserKeyAsync(userId); var userKey = await GetUserKeyAsync(userId);
@@ -1007,31 +997,6 @@ namespace Bit.Core.Services
return keyCreator(key); return keyCreator(key);
} }
private async Task<bool> ValidateUserKeyAsync(UserKey key, string userId = null)
{
if (key == null)
{
return false;
}
try
{
var encPrivateKey = await _stateService.GetPrivateKeyEncryptedAsync(userId);
if (encPrivateKey == null)
{
return false;
}
var privateKey = await DecryptToBytesAsync(new EncString(encPrivateKey), key);
await _cryptoFunctionService.RsaExtractPublicKeyAsync(privateKey);
return true;
}
catch
{
return false;
}
}
private class EncryptedObject private class EncryptedObject
{ {
public byte[] Iv { get; set; } public byte[] Iv { get; set; }
@@ -1054,10 +1019,6 @@ namespace Bit.Core.Services
// Decrypt // Decrypt
var masterKey = new MasterKey(Convert.FromBase64String(oldKey)); var masterKey = new MasterKey(Convert.FromBase64String(oldKey));
if (await IsLegacyUserAsync(masterKey, userId))
{
throw new LegacyUserException();
}
var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId); var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId);
if (encryptedUserKey == null) if (encryptedUserKey == null)
{ {
@@ -1097,10 +1058,6 @@ namespace Bit.Core.Services
kdfConfig, kdfConfig,
oldPinKey oldPinKey
); );
if (await IsLegacyUserAsync(masterKey))
{
throw new LegacyUserException();
}
var encUserKey = await _stateService.GetEncKeyEncryptedAsync(); var encUserKey = await _stateService.GetEncKeyEncryptedAsync();
var userKey = await DecryptUserKeyWithMasterKeyAsync( var userKey = await DecryptUserKeyWithMasterKeyAsync(
masterKey, masterKey,

View File

@@ -2,8 +2,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
@@ -68,8 +66,6 @@ namespace Bit.Core.Services
} }
if (!await _cryptoService.HasUserKeyAsync(userId)) if (!await _cryptoService.HasUserKeyAsync(userId))
{
try
{ {
if (!await _cryptoService.HasAutoUnlockKeyAsync(userId)) if (!await _cryptoService.HasAutoUnlockKeyAsync(userId))
{ {
@@ -77,16 +73,9 @@ namespace Bit.Core.Services
} }
if (userId != null && await _stateService.GetActiveUserIdAsync() != userId) if (userId != null && await _stateService.GetActiveUserIdAsync() != userId)
{ {
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), userId);
userId);
} }
} }
catch (LegacyUserException)
{
await LogOutAsync(false, userId);
}
}
// Check again to verify auto key was set // Check again to verify auto key was set
var hasKey = await _cryptoService.HasUserKeyAsync(userId); var hasKey = await _cryptoService.HasUserKeyAsync(userId);

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>2023.9.1</string> <string>2023.8.1</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>

View File

@@ -7,7 +7,6 @@ using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@@ -121,7 +120,8 @@ namespace Bit.iOS.Core.Controllers
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) || _pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent; _pinStatus == PinLockType.Persistent;
_biometricEnabled = await IsBiometricsEnabledAsync(); _biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
&& await _biometricService.CanUseBiometricsUnlockAsync();
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync(); _hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
_biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled; _biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled;
@@ -260,44 +260,12 @@ namespace Bit.iOS.Core.Controllers
&& &&
kdfConfig.Memory > Constants.MaximumArgon2IdMemoryBeforeExtensionCrashing kdfConfig.Memory > Constants.MaximumArgon2IdMemoryBeforeExtensionCrashing
&& &&
!await _platformUtilsService.ShowDialogAsync( !await _platformUtilsService.ShowDialogAsync(AppResources.UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve, AppResources.Warning, AppResources.Continue, AppResources.Cancel))
AppResources.UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve,
AppResources.Warning, AppResources.Continue, AppResources.Cancel))
{ {
return; return;
} }
if (_pinEnabled) if (_pinEnabled)
{
await UnlockWithPinAsync(inputtedValue, email, kdfConfig);
}
else
{
await UnlockWithMasterPasswordAsync(inputtedValue, email, kdfConfig);
}
}
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
finally
{
_checkingPassword = false;
}
}
private async Task HandleFailedCredentialsAsync()
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
await _accountManager.LogOutAsync(await _stateService.GetActiveUserIdAsync(), false, false);
return;
}
InvalidValue();
}
private async Task UnlockWithPinAsync(string inputPin, string email, KdfConfig kdfConfig)
{ {
var failed = true; var failed = true;
try try
@@ -321,7 +289,7 @@ namespace Bit.iOS.Core.Controllers
{ {
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync( userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockType.Transient, _pinStatus == PinLockType.Transient,
inputPin, inputtedValue,
email, email,
kdfConfig, kdfConfig,
oldPinProtected oldPinProtected
@@ -330,7 +298,7 @@ namespace Bit.iOS.Core.Controllers
else else
{ {
userKey = await _cryptoService.DecryptUserKeyWithPinAsync( userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
inputPin, inputtedValue,
email, email,
kdfConfig, kdfConfig,
userKeyPin userKeyPin
@@ -339,7 +307,7 @@ namespace Bit.iOS.Core.Controllers
var protectedPin = await _stateService.GetProtectedPinAsync(); var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey); var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != inputPin; failed = decryptedPin != inputtedValue;
if (!failed) if (!failed)
{ {
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
@@ -350,20 +318,14 @@ namespace Bit.iOS.Core.Controllers
{ {
failed = true; failed = true;
} }
if (failed) if (failed)
{ {
await HandleFailedCredentialsAsync(); await HandleFailedCredentialsAsync();
} }
} }
else
private async Task UnlockWithMasterPasswordAsync(string inputPassword, string email, KdfConfig kdfConfig)
{ {
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputPassword, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
if (await _cryptoService.IsLegacyUserAsync(masterKey))
{
throw new LegacyUserException();
}
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync(); var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
if (storedPasswordHash == null) if (storedPasswordHash == null)
@@ -371,15 +333,12 @@ namespace Bit.iOS.Core.Controllers
var oldKey = await _secureStorageService.GetAsync<string>("oldKey"); var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (masterKey.KeyB64 == oldKey) if (masterKey.KeyB64 == oldKey)
{ {
var localPasswordHash = var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
await _cryptoService.HashMasterKeyAsync(inputPassword, masterKey,
HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey"); await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash); await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
} }
} }
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputPassword, masterKey);
if (passwordValid) if (passwordValid)
{ {
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
@@ -393,16 +352,30 @@ namespace Bit.iOS.Core.Controllers
await HandleFailedCredentialsAsync(); await HandleFailedCredentialsAsync();
} }
} }
}
finally
{
_checkingPassword = false;
}
}
private async Task HandleFailedCredentialsAsync()
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
await _accountManager.LogOutAsync(await _stateService.GetActiveUserIdAsync(), false, false);
return;
}
InvalidValue();
}
public async Task PromptBiometricAsync() public async Task PromptBiometricAsync()
{
try
{ {
if (!_biometricEnabled || !_biometricIntegrityValid) if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword, _pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
@@ -413,11 +386,6 @@ namespace Bit.iOS.Core.Controllers
await SetKeyAndContinueAsync(userKey); await SetKeyAndContinueAsync(userKey);
} }
} }
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
}
public void PromptSSO() public void PromptSSO()
{ {
@@ -468,29 +436,6 @@ namespace Bit.iOS.Core.Controllers
} }
} }
private async Task<bool> IsBiometricsEnabledAsync()
{
try
{
return await _vaultTimeoutService.IsBiometricLockSetAsync() &&
await _biometricService.CanUseBiometricsUnlockAsync();
}
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
return false;
}
private async Task HandleLegacyUserAsync()
{
// Legacy users must migrate on web vault.
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong,
AppResources.AnErrorHasOccurred,
AppResources.Ok);
await _vaultTimeoutService.LogOutAsync();
}
private void InvalidValue() private void InvalidValue()
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,

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>2023.9.1</string> <string>2023.8.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>2023.9.1</string> <string>2023.8.1</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>

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>2023.9.1</string> <string>2023.8.1</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleIconName</key> <key>CFBundleIconName</key>