mirror of
https://github.com/bitwarden/mobile
synced 2026-01-06 02:23:57 +00:00
Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts: # src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs # src/App/Resources/AppResources.resx # src/Core/Abstractions/IStateService.cs
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Utilities.Prompts;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
|
||||
@@ -18,6 +19,7 @@ namespace Bit.App.Abstractions
|
||||
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
|
||||
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
|
||||
bool autofocus = true, bool password = false);
|
||||
Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config);
|
||||
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
|
||||
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
<Folder Include="Controls\IconLabelButton\" />
|
||||
<Folder Include="Controls\PasswordStrengthProgressBar\" />
|
||||
<Folder Include="Utilities\Automation\" />
|
||||
<Folder Include="Utilities\Prompts\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -442,5 +443,6 @@
|
||||
<None Remove="MessagePack.MSBuild.Tasks" />
|
||||
<None Remove="Controls\PasswordStrengthProgressBar\" />
|
||||
<None Remove="Utilities\Automation\" />
|
||||
<None Remove="Utilities\Prompts\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Controls
|
||||
{
|
||||
@@ -8,6 +7,7 @@ namespace Bit.App.Controls
|
||||
public CustomLabel()
|
||||
{
|
||||
}
|
||||
|
||||
public int? FontWeight { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<StackLayout StyleClass="box">
|
||||
<Grid
|
||||
StyleClass="box-row"
|
||||
IsVisible="{Binding PinLock}"
|
||||
IsVisible="{Binding PinEnabled}"
|
||||
Padding="0, 10, 0, 0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -89,7 +89,7 @@
|
||||
<Grid
|
||||
x:Name="_passwordGrid"
|
||||
StyleClass="box-row"
|
||||
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
|
||||
IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
|
||||
Padding="0, 10, 0, 0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_vm?.PinLock ?? false)
|
||||
if (_vm?.PinEnabled ?? false)
|
||||
{
|
||||
return _pin;
|
||||
}
|
||||
@@ -54,7 +54,7 @@ namespace Bit.App.Pages
|
||||
|
||||
public async Task PromptBiometricAfterResumeAsync()
|
||||
{
|
||||
if (_vm.BiometricLock)
|
||||
if (_vm.BiometricEnabled)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
if (!_promptedAfterResume)
|
||||
@@ -91,13 +91,13 @@ namespace Bit.App.Pages
|
||||
|
||||
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
||||
|
||||
if (!_vm.BiometricLock)
|
||||
if (!_vm.BiometricEnabled)
|
||||
{
|
||||
RequestFocus(SecretEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_vm.UsingKeyConnector && !_vm.PinLock)
|
||||
if (_vm.UsingKeyConnector && !_vm.PinEnabled)
|
||||
{
|
||||
_passwordGrid.IsVisible = false;
|
||||
_unlockButton.IsVisible = false;
|
||||
|
||||
@@ -38,16 +38,15 @@ namespace Bit.App.Pages
|
||||
private string _masterPassword;
|
||||
private string _pin;
|
||||
private bool _showPassword;
|
||||
private bool _pinLock;
|
||||
private bool _biometricLock;
|
||||
private PinLockEnum _pinStatus;
|
||||
private bool _pinEnabled;
|
||||
private bool _biometricEnabled;
|
||||
private bool _biometricIntegrityValid = true;
|
||||
private bool _biometricButtonVisible;
|
||||
private bool _usingKeyConnector;
|
||||
private string _biometricButtonText;
|
||||
private string _loggedInAsText;
|
||||
private string _lockedVerifyText;
|
||||
private bool _isPinProtected;
|
||||
private bool _isPinProtectedWithKey;
|
||||
|
||||
public LockPageViewModel()
|
||||
{
|
||||
@@ -101,10 +100,10 @@ namespace Bit.App.Pages
|
||||
});
|
||||
}
|
||||
|
||||
public bool PinLock
|
||||
public bool PinEnabled
|
||||
{
|
||||
get => _pinLock;
|
||||
set => SetProperty(ref _pinLock, value);
|
||||
get => _pinEnabled;
|
||||
set => SetProperty(ref _pinEnabled, value);
|
||||
}
|
||||
|
||||
public bool UsingKeyConnector
|
||||
@@ -112,10 +111,10 @@ namespace Bit.App.Pages
|
||||
get => _usingKeyConnector;
|
||||
}
|
||||
|
||||
public bool BiometricLock
|
||||
public bool BiometricEnabled
|
||||
{
|
||||
get => _biometricLock;
|
||||
set => SetProperty(ref _biometricLock, value);
|
||||
get => _biometricEnabled;
|
||||
set => SetProperty(ref _biometricEnabled, value);
|
||||
}
|
||||
|
||||
public bool BiometricIntegrityValid
|
||||
@@ -163,14 +162,18 @@ namespace Bit.App.Pages
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||
_isPinProtectedWithKey;
|
||||
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
|
||||
_pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
|
||||
var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
|
||||
?? await _stateService.GetPinProtectedKeyAsync();
|
||||
PinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
|
||||
_pinStatus == PinLockEnum.Persistent;
|
||||
|
||||
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasEncryptedUserKeyAsync();
|
||||
|
||||
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||
if (_usingKeyConnector && !(BiometricLock || PinLock))
|
||||
if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
|
||||
{
|
||||
await _vaultTimeoutService.LogOutAsync();
|
||||
return;
|
||||
@@ -189,7 +192,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
var webVaultHostname = CoreHelpers.GetHostname(webVault);
|
||||
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
|
||||
if (PinLock)
|
||||
if (PinEnabled)
|
||||
{
|
||||
PageTitle = AppResources.VerifyPIN;
|
||||
LockedVerifyText = AppResources.VaultLockedPIN;
|
||||
@@ -208,7 +211,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
if (BiometricLock)
|
||||
if (BiometricEnabled)
|
||||
{
|
||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||
if (!_biometricIntegrityValid)
|
||||
@@ -230,14 +233,14 @@ namespace Bit.App.Pages
|
||||
|
||||
public async Task SubmitAsync()
|
||||
{
|
||||
if (PinLock && string.IsNullOrWhiteSpace(Pin))
|
||||
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
|
||||
AppResources.Ok);
|
||||
return;
|
||||
}
|
||||
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword))
|
||||
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
|
||||
{
|
||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
||||
@@ -248,34 +251,54 @@ namespace Bit.App.Pages
|
||||
ShowPassword = false;
|
||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
|
||||
if (PinLock)
|
||||
if (PinEnabled)
|
||||
{
|
||||
var failed = true;
|
||||
try
|
||||
{
|
||||
if (_isPinProtected)
|
||||
EncString userKeyPin = null;
|
||||
EncString oldPinProtected = null;
|
||||
if (_pinStatus == PinLockEnum.Persistent)
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
|
||||
userKeyPin = await _stateService.GetUserKeyPinAsync();
|
||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||
}
|
||||
else if (_pinStatus == PinLockEnum.Transient)
|
||||
{
|
||||
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
|
||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||
}
|
||||
|
||||
UserKey userKey;
|
||||
if (oldPinProtected != null)
|
||||
{
|
||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||
_pinStatus == PinLockEnum.Transient,
|
||||
Pin,
|
||||
_email,
|
||||
kdfConfig,
|
||||
await _stateService.GetPinProtectedKeyAsync());
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||
failed = decPin != Pin;
|
||||
if (!failed)
|
||||
{
|
||||
Pin = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
}
|
||||
oldPinProtected
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
|
||||
failed = false;
|
||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||
Pin,
|
||||
_email,
|
||||
kdfConfig,
|
||||
userKeyPin
|
||||
);
|
||||
}
|
||||
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||
failed = decryptedPin != Pin;
|
||||
if (!failed)
|
||||
{
|
||||
Pin = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
await SetKeyAndContinueAsync(userKey);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -296,19 +319,21 @@ namespace Bit.App.Pages
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
|
||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
||||
var storedKeyHash = await _cryptoService.GetPasswordHashAsync();
|
||||
var passwordValid = false;
|
||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||
|
||||
if (storedKeyHash != null)
|
||||
{
|
||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
|
||||
// Offline unlock possible
|
||||
passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(MasterPassword, masterKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Online unlock required
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
|
||||
var request = new PasswordVerificationRequest();
|
||||
request.MasterPasswordHash = keyHash;
|
||||
|
||||
@@ -317,8 +342,8 @@ namespace Bit.App.Pages
|
||||
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
||||
passwordValid = true;
|
||||
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
|
||||
await _cryptoService.SetPasswordHashAsync(localKeyHash);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -328,15 +353,6 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (passwordValid)
|
||||
{
|
||||
if (_isPinProtected)
|
||||
{
|
||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
|
||||
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
|
||||
}
|
||||
|
||||
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
||||
{
|
||||
// Save the ForcePasswordResetReason to force a password reset after unlock
|
||||
@@ -346,10 +362,13 @@ namespace Bit.App.Pages
|
||||
|
||||
MasterPassword = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
|
||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||
await SetKeyAndContinueAsync(userKey);
|
||||
|
||||
// Re-enable biometrics
|
||||
if (BiometricLock & !BiometricIntegrityValid)
|
||||
if (BiometricEnabled & !BiometricIntegrityValid)
|
||||
{
|
||||
await _biometricService.SetupBiometricAsync();
|
||||
}
|
||||
@@ -426,7 +445,7 @@ namespace Bit.App.Pages
|
||||
public void TogglePassword()
|
||||
{
|
||||
ShowPassword = !ShowPassword;
|
||||
var secret = PinLock ? Pin : MasterPassword;
|
||||
var secret = PinEnabled ? Pin : MasterPassword;
|
||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
||||
}
|
||||
|
||||
@@ -434,12 +453,12 @@ namespace Bit.App.Pages
|
||||
{
|
||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||
BiometricButtonVisible = BiometricIntegrityValid;
|
||||
if (!BiometricLock || !BiometricIntegrityValid)
|
||||
if (!BiometricEnabled || !BiometricIntegrityValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
PinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
||||
await _stateService.SetBiometricLockedAsync(!success);
|
||||
if (success)
|
||||
@@ -448,12 +467,12 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
|
||||
private async Task SetKeyAndContinueAsync(UserKey key)
|
||||
{
|
||||
var hasKey = await _cryptoService.HasKeyAsync();
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (!hasKey)
|
||||
{
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
await _cryptoService.SetUserKeyAsync(key);
|
||||
}
|
||||
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
|
||||
await DoContinueAsync();
|
||||
|
||||
@@ -177,25 +177,28 @@ namespace Bit.App.Pages
|
||||
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
|
||||
Email = Email.Trim().ToLower();
|
||||
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig);
|
||||
var encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
|
||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
|
||||
newMasterKey,
|
||||
await _cryptoService.MakeUserKeyAsync()
|
||||
);
|
||||
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey);
|
||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
||||
var request = new RegisterRequest
|
||||
{
|
||||
Email = Email,
|
||||
Name = Name,
|
||||
MasterPasswordHash = hashedPassword,
|
||||
MasterPasswordHint = Hint,
|
||||
Key = encKey.Item2.EncryptedString,
|
||||
Key = newProtectedUserKey.EncryptedString,
|
||||
Kdf = kdfConfig.Type,
|
||||
KdfIterations = kdfConfig.Iterations,
|
||||
KdfMemory = kdfConfig.Memory,
|
||||
KdfParallelism = kdfConfig.Parallelism,
|
||||
Keys = new KeysRequest
|
||||
{
|
||||
PublicKey = keys.Item1,
|
||||
EncryptedPrivateKey = keys.Item2.EncryptedString
|
||||
PublicKey = newPublicKey,
|
||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
||||
},
|
||||
CaptchaResponse = _captchaToken,
|
||||
};
|
||||
|
||||
@@ -165,26 +165,18 @@ namespace Bit.App.Pages
|
||||
|
||||
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
||||
var email = await _stateService.GetEmailAsync();
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
|
||||
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
|
||||
|
||||
Tuple<SymmetricCryptoKey, EncString> encKey;
|
||||
var existingEncKey = await _cryptoService.GetEncKeyAsync();
|
||||
if (existingEncKey == null)
|
||||
{
|
||||
encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
encKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||
}
|
||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
|
||||
await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());
|
||||
|
||||
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||
var keys = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
||||
var request = new SetPasswordRequest
|
||||
{
|
||||
MasterPasswordHash = masterPasswordHash,
|
||||
Key = encKey.Item2.EncryptedString,
|
||||
Key = newProtectedUserKey.EncryptedString,
|
||||
MasterPasswordHint = Hint,
|
||||
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
|
||||
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
|
||||
@@ -204,19 +196,19 @@ namespace Bit.App.Pages
|
||||
// Set Password and relevant information
|
||||
await _apiService.SetPasswordAsync(request);
|
||||
await _stateService.SetKdfConfigurationAsync(kdfConfig);
|
||||
await _cryptoService.SetKeyAsync(key);
|
||||
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
|
||||
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
|
||||
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
||||
await _cryptoService.SetPasswordHashAsync(localMasterPasswordHash);
|
||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
|
||||
await _cryptoService.SetPrivateKeyAsync(keys.Item2.EncryptedString);
|
||||
|
||||
if (ResetPasswordAutoEnroll)
|
||||
{
|
||||
// Grab Organization Keys
|
||||
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
|
||||
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
|
||||
// Grab user's Encryption Key and encrypt with Org Public Key
|
||||
var userEncKey = await _cryptoService.GetEncKeyAsync();
|
||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey);
|
||||
// Grab User Key and encrypt with Org Public Key
|
||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, publicKey);
|
||||
// Request
|
||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||
{
|
||||
|
||||
@@ -93,12 +93,12 @@ namespace Bit.App.Pages
|
||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
var email = await _stateService.GetEmailAsync();
|
||||
|
||||
// Create new key and hash new password
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||
// Create new master key and hash new password
|
||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey);
|
||||
|
||||
// Create new encKey for the User
|
||||
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||
// Encrypt user key with new master key
|
||||
var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
|
||||
|
||||
// Initiate API action
|
||||
try
|
||||
@@ -108,10 +108,10 @@ namespace Bit.App.Pages
|
||||
switch (_reason)
|
||||
{
|
||||
case ForcePasswordResetReason.AdminForcePasswordReset:
|
||||
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||
await UpdateTempPasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
||||
break;
|
||||
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
||||
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||
await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
85
src/App/Pages/Settings/BlockAutofillUrisPage.xaml
Normal file
85
src/App/Pages/Settings/BlockAutofillUrisPage.xaml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="Bit.App.Pages.BlockAutofillUrisPage"
|
||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
||||
x:DataType="pages:BlockAutofillUrisPageViewModel"
|
||||
Title="{u:I18n BlockAutoFill}">
|
||||
<ContentPage.BindingContext>
|
||||
<pages:BlockAutofillUrisPageViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
<ContentPage.Resources>
|
||||
<ResourceDictionary>
|
||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
||||
</ResourceDictionary>
|
||||
</ContentPage.Resources>
|
||||
<StackLayout Orientation="Vertical">
|
||||
<Image
|
||||
x:Name="_emptyUrisPlaceholder"
|
||||
HorizontalOptions="Center"
|
||||
WidthRequest="120"
|
||||
HeightRequest="120"
|
||||
Margin="0,100,0,0"
|
||||
IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ThereAreNoBlockedURIs}" />
|
||||
<controls:CustomLabel
|
||||
StyleClass="box-label-regular"
|
||||
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
|
||||
FontWeight="500"
|
||||
HorizontalTextAlignment="Center"
|
||||
Margin="14,10,14,0"/>
|
||||
<controls:ExtendedCollectionView
|
||||
ItemsSource="{Binding BlockedUris}"
|
||||
IsVisible="{Binding ShowList}"
|
||||
VerticalOptions="FillAndExpand"
|
||||
Margin="0,5,0,0"
|
||||
SelectionMode="None"
|
||||
StyleClass="list, list-platform"
|
||||
ExtraDataForLogging="Blocked Autofill Uris"
|
||||
AutomationId="BlockedUrisCellList">
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="pages:BlockAutofillUriItemViewModel">
|
||||
<StackLayout
|
||||
Orientation="Vertical"
|
||||
AutomationId="BlockedUriCell">
|
||||
<StackLayout
|
||||
Orientation="Horizontal">
|
||||
<controls:CustomLabel
|
||||
VerticalOptions="Center"
|
||||
StyleClass="box-label-regular"
|
||||
Text="{Binding Uri}"
|
||||
MaxLines="2"
|
||||
LineBreakMode="TailTruncation"
|
||||
FontWeight="500"
|
||||
Margin="15,0,0,0"
|
||||
HorizontalOptions="StartAndExpand"/>
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button-muted, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.PencilSquare}}"
|
||||
Command="{Binding EditUriCommand}"
|
||||
Margin="5,0,15,0"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n EditURI}"
|
||||
AutomationId="EditUriButton" />
|
||||
</StackLayout>
|
||||
<BoxView StyleClass="box-row-separator" />
|
||||
</StackLayout>
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</controls:ExtendedCollectionView>
|
||||
<Button
|
||||
Text="{u:I18n NewBlockedURI}"
|
||||
Command="{Binding AddUriCommand}"
|
||||
VerticalOptions="End"
|
||||
HeightRequest="40"
|
||||
Opacity="0.8"
|
||||
Margin="14,5,14,10"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n NewBlockedURI}"
|
||||
AutomationId="NewBlockedUriButton" />
|
||||
</StackLayout>
|
||||
</pages:BaseContentPage>
|
||||
44
src/App/Pages/Settings/BlockAutofillUrisPage.xaml.cs
Normal file
44
src/App/Pages/Settings/BlockAutofillUrisPage.xaml.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Styles;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public partial class BlockAutofillUrisPage : BaseContentPage, IThemeDirtablePage
|
||||
{
|
||||
private readonly BlockAutofillUrisPageViewModel _vm;
|
||||
|
||||
public BlockAutofillUrisPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_vm = BindingContext as BlockAutofillUrisPageViewModel;
|
||||
_vm.Page = this;
|
||||
}
|
||||
|
||||
protected override void OnAppearing()
|
||||
{
|
||||
base.OnAppearing();
|
||||
|
||||
_vm.InitAsync().FireAndForget(_ => Navigation.PopAsync());
|
||||
|
||||
UpdatePlaceholder();
|
||||
}
|
||||
|
||||
public override async Task UpdateOnThemeChanged()
|
||||
{
|
||||
await base.UpdateOnThemeChanged();
|
||||
|
||||
UpdatePlaceholder();
|
||||
}
|
||||
|
||||
private void UpdatePlaceholder()
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
_emptyUrisPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_uris_placeholder" : "empty_uris_placeholder_dark"));
|
||||
}
|
||||
}
|
||||
}
|
||||
186
src/App/Pages/Settings/BlockAutofillUrisPageViewModel.cs
Normal file
186
src/App/Pages/Settings/BlockAutofillUrisPageViewModel.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class BlockAutofillUrisPageViewModel : BaseViewModel
|
||||
{
|
||||
private const char URI_SEPARARTOR = ',';
|
||||
private const string URI_FORMAT = "https://domain.com";
|
||||
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IDeviceActionService _deviceActionService;
|
||||
|
||||
public BlockAutofillUrisPageViewModel()
|
||||
{
|
||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
|
||||
|
||||
AddUriCommand = new AsyncCommand(AddUriAsync,
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
EditUriCommand = new AsyncCommand<BlockAutofillUriItemViewModel>(EditUriAsync,
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
}
|
||||
|
||||
public ObservableRangeCollection<BlockAutofillUriItemViewModel> BlockedUris { get; set; } = new ObservableRangeCollection<BlockAutofillUriItemViewModel>();
|
||||
|
||||
public bool ShowList => BlockedUris.Any();
|
||||
|
||||
public ICommand AddUriCommand { get; }
|
||||
|
||||
public ICommand EditUriCommand { get; }
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
||||
if (blockedUrisList?.Any() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
BlockedUris.AddRange(blockedUrisList.OrderBy(uri => uri).Select(u => new BlockAutofillUriItemViewModel(u, EditUriCommand)).ToList());
|
||||
TriggerPropertyChanged(nameof(ShowList));
|
||||
});
|
||||
}
|
||||
|
||||
private async Task AddUriAsync()
|
||||
{
|
||||
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
|
||||
{
|
||||
Title = AppResources.NewUri,
|
||||
Subtitle = AppResources.EnterURI,
|
||||
ValueSubInfo = string.Format(AppResources.FormatXSeparateMultipleURIsWithAComma, URI_FORMAT),
|
||||
OkButtonText = AppResources.Save,
|
||||
ValidateText = text => ValidateUris(text, true)
|
||||
});
|
||||
if (response?.Text is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
foreach (var uri in response.Value.Text.Split(URI_SEPARARTOR).Where(s => !string.IsNullOrEmpty(s)))
|
||||
{
|
||||
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
|
||||
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
|
||||
}
|
||||
|
||||
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
|
||||
TriggerPropertyChanged(nameof(BlockedUris));
|
||||
TriggerPropertyChanged(nameof(ShowList));
|
||||
});
|
||||
await UpdateAutofillBlacklistedUrisAsync();
|
||||
_deviceActionService.Toast(AppResources.URISaved);
|
||||
}
|
||||
|
||||
private async Task EditUriAsync(BlockAutofillUriItemViewModel uriItemViewModel)
|
||||
{
|
||||
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
|
||||
{
|
||||
Title = AppResources.EditURI,
|
||||
Subtitle = AppResources.EnterURI,
|
||||
Text = uriItemViewModel.Uri,
|
||||
ValueSubInfo = string.Format(AppResources.FormatX, URI_FORMAT),
|
||||
OkButtonText = AppResources.Save,
|
||||
ThirdButtonText = AppResources.Remove,
|
||||
ValidateText = text => ValidateUris(text, false)
|
||||
});
|
||||
if (response is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.Value.ExecuteThirdAction)
|
||||
{
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
BlockedUris.Remove(uriItemViewModel);
|
||||
TriggerPropertyChanged(nameof(ShowList));
|
||||
});
|
||||
await UpdateAutofillBlacklistedUrisAsync();
|
||||
_deviceActionService.Toast(AppResources.URIRemoved);
|
||||
return;
|
||||
}
|
||||
|
||||
var cleanedUri = response.Value.Text.Replace(Environment.NewLine, string.Empty).Trim();
|
||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
BlockedUris.Remove(uriItemViewModel);
|
||||
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
|
||||
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
|
||||
TriggerPropertyChanged(nameof(BlockedUris));
|
||||
TriggerPropertyChanged(nameof(ShowList));
|
||||
});
|
||||
await UpdateAutofillBlacklistedUrisAsync();
|
||||
_deviceActionService.Toast(AppResources.URISaved);
|
||||
}
|
||||
|
||||
private string ValidateUris(string uris, bool allowMultipleUris)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(uris))
|
||||
{
|
||||
return string.Format(AppResources.FormatX, URI_FORMAT);
|
||||
}
|
||||
|
||||
if (!allowMultipleUris && uris.Contains(URI_SEPARARTOR))
|
||||
{
|
||||
return AppResources.CannotEditMultipleURIsAtOnce;
|
||||
}
|
||||
|
||||
foreach (var uri in uris.Split(URI_SEPARARTOR).Where(u => !string.IsNullOrWhiteSpace(u)))
|
||||
{
|
||||
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
|
||||
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
|
||||
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
|
||||
{
|
||||
return AppResources.InvalidFormatUseHttpsHttpOrAndroidApp;
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(cleanedUri, UriKind.Absolute, out var _))
|
||||
{
|
||||
return AppResources.InvalidURI;
|
||||
}
|
||||
|
||||
if (BlockedUris.Any(uriItem => uriItem.Uri == cleanedUri))
|
||||
{
|
||||
return string.Format(AppResources.TheURIXIsAlreadyBlocked, cleanedUri);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task UpdateAutofillBlacklistedUrisAsync()
|
||||
{
|
||||
await _stateService.SetAutofillBlacklistedUrisAsync(BlockedUris.Any() ? BlockedUris.Select(bu => bu.Uri).ToList() : null);
|
||||
}
|
||||
}
|
||||
|
||||
public class BlockAutofillUriItemViewModel : ExtendedViewModel
|
||||
{
|
||||
public BlockAutofillUriItemViewModel(string uri, ICommand editUriCommand)
|
||||
{
|
||||
Uri = uri;
|
||||
EditUriCommand = new Command(() => editUriCommand.Execute(this));
|
||||
}
|
||||
|
||||
public string Uri { get; }
|
||||
|
||||
public ICommand EditUriCommand { get; }
|
||||
}
|
||||
}
|
||||
@@ -153,22 +153,14 @@
|
||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||
</StackLayout>
|
||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
||||
<StackLayout StyleClass="box-row, box-row-input">
|
||||
<Label
|
||||
Text="{u:I18n AutofillBlockedUris}"
|
||||
StyleClass="box-label" />
|
||||
<Editor
|
||||
x:Name="_autofillBlockedUrisEditor"
|
||||
Text="{Binding AutofillBlockedUris}"
|
||||
StyleClass="box-value"
|
||||
AutoSize="TextChanges"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsTextPredictionEnabled="False"
|
||||
Keyboard="Url"
|
||||
Unfocused="AutofillBlockedUrisEditor_Unfocused" />
|
||||
</StackLayout>
|
||||
<StackLayout.GestureRecognizers>
|
||||
<TapGestureRecognizer Command="{Binding GoToBlockAutofillUrisCommand}" />
|
||||
</StackLayout.GestureRecognizers>
|
||||
<Label
|
||||
Text="{u:I18n AutofillBlockedUrisDescription}"
|
||||
Text="{u:I18n BlockAutoFill}"
|
||||
StyleClass="box-label-regular" />
|
||||
<Label
|
||||
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
|
||||
StyleClass="box-footer-label" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.PlatformConfiguration;
|
||||
@@ -44,17 +42,6 @@ namespace Bit.App.Pages
|
||||
await _vm.InitAsync();
|
||||
}
|
||||
|
||||
protected async override void OnDisappearing()
|
||||
{
|
||||
base.OnDisappearing();
|
||||
await _vm.UpdateAutofillBlockedUris();
|
||||
}
|
||||
|
||||
private async void AutofillBlockedUrisEditor_Unfocused(object sender, FocusEventArgs e)
|
||||
{
|
||||
await _vm.UpdateAutofillBlockedUris();
|
||||
}
|
||||
|
||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
||||
{
|
||||
if (DoOnce())
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
@@ -19,7 +20,6 @@ namespace Bit.App.Pages
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
|
||||
private bool _autofillSavePrompt;
|
||||
private string _autofillBlockedUris;
|
||||
private bool _favicon;
|
||||
private bool _autoTotpCopy;
|
||||
private int _clearClipboardSelectedIndex;
|
||||
@@ -84,6 +84,10 @@ namespace Bit.App.Pages
|
||||
new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
|
||||
};
|
||||
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
|
||||
|
||||
GoToBlockAutofillUrisCommand = new AsyncCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()),
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
}
|
||||
|
||||
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
||||
@@ -192,25 +196,18 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
public string AutofillBlockedUris
|
||||
{
|
||||
get => _autofillBlockedUris;
|
||||
set => SetProperty(ref _autofillBlockedUris, value);
|
||||
}
|
||||
|
||||
public bool ShowAndroidAutofillSettings
|
||||
{
|
||||
get => _showAndroidAutofillSettings;
|
||||
set => SetProperty(ref _showAndroidAutofillSettings, value);
|
||||
}
|
||||
|
||||
public ICommand GoToBlockAutofillUrisCommand { get; }
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
||||
|
||||
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
||||
AutofillBlockedUris = blockedUrisList != null ? string.Join(", ", blockedUrisList) : null;
|
||||
|
||||
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
|
||||
|
||||
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||
@@ -288,41 +285,6 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateAutofillBlockedUris()
|
||||
{
|
||||
if (_inited)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(AutofillBlockedUris))
|
||||
{
|
||||
await _stateService.SetAutofillBlacklistedUrisAsync(null);
|
||||
AutofillBlockedUris = null;
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var csv = AutofillBlockedUris;
|
||||
var urisList = new List<string>();
|
||||
foreach (var uri in csv.Split(','))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var cleanedUri = uri.Replace(System.Environment.NewLine, string.Empty).Trim();
|
||||
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
|
||||
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
urisList.Add(cleanedUri);
|
||||
}
|
||||
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
|
||||
AutofillBlockedUris = string.Join(", ", urisList);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateCurrentLocaleAsync()
|
||||
{
|
||||
if (!_inited)
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Bit.App.Pages
|
||||
_cipherDomain = await _cipherService.GetAsync(CipherId);
|
||||
Cipher = await _cipherDomain.DecryptAsync();
|
||||
LoadAttachments();
|
||||
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
|
||||
_hasUpdatedKey = await _cryptoService.HasUserKeyAsync();
|
||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
|
||||
if (!_canAccessAttachments)
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||
IsVisible="{Binding Cipher.ViewPassword}"
|
||||
AutomationId="ViewValueButton" />
|
||||
AutomationId="ShowValueButton" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||
|
||||
@@ -208,6 +208,11 @@ namespace Bit.App.Pages
|
||||
}
|
||||
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
|
||||
{
|
||||
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (selection == AppResources.AutofillAndSave)
|
||||
{
|
||||
var uris = cipher.Login?.Uris?.ToList();
|
||||
|
||||
@@ -330,13 +330,16 @@ namespace Bit.App.Pages
|
||||
items.AddRange(itemGroup);
|
||||
}
|
||||
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||
// because of update to XF v5.0.0.2401
|
||||
GroupedItems.Clear();
|
||||
}
|
||||
GroupedItems.ReplaceRange(items);
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||
// because of update to XF v5.0.0.2401
|
||||
GroupedItems.Clear();
|
||||
}
|
||||
GroupedItems.ReplaceRange(items);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -356,21 +359,24 @@ namespace Bit.App.Pages
|
||||
items.AddRange(itemGroup);
|
||||
}
|
||||
|
||||
if (groupedItems.Any())
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
if (groupedItems.Any())
|
||||
{
|
||||
if (Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||
// because of update to XF v5.0.0.2401
|
||||
GroupedItems.Clear();
|
||||
}
|
||||
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||
GroupedItems.AddRange(items);
|
||||
}
|
||||
else
|
||||
{
|
||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
||||
// because of update to XF v5.0.0.2401
|
||||
GroupedItems.Clear();
|
||||
}
|
||||
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
|
||||
GroupedItems.AddRange(items);
|
||||
}
|
||||
else
|
||||
{
|
||||
GroupedItems.Clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -378,9 +384,12 @@ namespace Bit.App.Pages
|
||||
_doingLoad = false;
|
||||
Loaded = true;
|
||||
Loading = false;
|
||||
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
|
||||
ShowList = !ShowNoData;
|
||||
DisableRefreshing();
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
|
||||
ShowList = !ShowNoData;
|
||||
DisableRefreshing();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
135
src/App/Resources/AppResources.Designer.cs
generated
135
src/App/Resources/AppResources.Designer.cs
generated
@@ -823,15 +823,6 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android"..
|
||||
/// </summary>
|
||||
public static string AutofillBlockedUrisDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("AutofillBlockedUrisDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Do you want to auto-fill or view this item?.
|
||||
/// </summary>
|
||||
@@ -967,6 +958,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Auto-fill will not be offered for these URIs..
|
||||
/// </summary>
|
||||
public static string AutoFillWillNotBeOfferedForTheseURIs {
|
||||
get {
|
||||
return ResourceManager.GetString("AutoFillWillNotBeOfferedForTheseURIs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Auto-fill with Bitwarden.
|
||||
/// </summary>
|
||||
@@ -1255,6 +1255,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Block auto-fill.
|
||||
/// </summary>
|
||||
public static string BlockAutoFill {
|
||||
get {
|
||||
return ResourceManager.GetString("BlockAutoFill", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Brand.
|
||||
/// </summary>
|
||||
@@ -1291,6 +1300,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cannot edit multiple URIs at once.
|
||||
/// </summary>
|
||||
public static string CannotEditMultipleURIsAtOnce {
|
||||
get {
|
||||
return ResourceManager.GetString("CannotEditMultipleURIsAtOnce", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cannot open the app "{0}"..
|
||||
/// </summary>
|
||||
@@ -2173,6 +2191,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit URI.
|
||||
/// </summary>
|
||||
public static string EditURI {
|
||||
get {
|
||||
return ResourceManager.GetString("EditURI", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Email.
|
||||
/// </summary>
|
||||
@@ -2326,6 +2353,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Enter URI.
|
||||
/// </summary>
|
||||
public static string EnterURI {
|
||||
get {
|
||||
return ResourceManager.GetString("EnterURI", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Enter the 6 digit verification code from your authenticator app..
|
||||
/// </summary>
|
||||
@@ -2974,6 +3010,24 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Format: {0}.
|
||||
/// </summary>
|
||||
public static string FormatX {
|
||||
get {
|
||||
return ResourceManager.GetString("FormatX", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Format: {0}. Separate multiple URIs with a comma..
|
||||
/// </summary>
|
||||
public static string FormatXSeparateMultipleURIsWithAComma {
|
||||
get {
|
||||
return ResourceManager.GetString("FormatXSeparateMultipleURIsWithAComma", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Forwarded email alias.
|
||||
/// </summary>
|
||||
@@ -3316,6 +3370,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid format. Use https://, http://, or androidapp://.
|
||||
/// </summary>
|
||||
public static string InvalidFormatUseHttpsHttpOrAndroidApp {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidFormatUseHttpsHttpOrAndroidApp", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid master password. Try again..
|
||||
/// </summary>
|
||||
@@ -3334,6 +3397,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid URI.
|
||||
/// </summary>
|
||||
public static string InvalidURI {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidURI", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid verification code.
|
||||
/// </summary>
|
||||
@@ -4263,6 +4335,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New blocked URI.
|
||||
/// </summary>
|
||||
public static string NewBlockedURI {
|
||||
get {
|
||||
return ResourceManager.GetString("NewBlockedURI", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to New custom field.
|
||||
/// </summary>
|
||||
@@ -6182,6 +6263,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There are no blocked URIs.
|
||||
/// </summary>
|
||||
public static string ThereAreNoBlockedURIs {
|
||||
get {
|
||||
return ResourceManager.GetString("ThereAreNoBlockedURIs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There are no items in your vault that match "{0}".
|
||||
/// </summary>
|
||||
@@ -6200,6 +6290,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The URI {0} is already blocked.
|
||||
/// </summary>
|
||||
public static string TheURIXIsAlreadyBlocked {
|
||||
get {
|
||||
return ResourceManager.GetString("TheURIXIsAlreadyBlocked", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 30 days.
|
||||
/// </summary>
|
||||
@@ -6668,6 +6767,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to URI removed.
|
||||
/// </summary>
|
||||
public static string URIRemoved {
|
||||
get {
|
||||
return ResourceManager.GetString("URIRemoved", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to URIs.
|
||||
/// </summary>
|
||||
@@ -6677,6 +6785,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to URI saved.
|
||||
/// </summary>
|
||||
public static string URISaved {
|
||||
get {
|
||||
return ResourceManager.GetString("URISaved", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to US.
|
||||
/// </summary>
|
||||
|
||||
@@ -1585,9 +1585,6 @@ Scanning will happen automatically.</value>
|
||||
<data name="AutofillBlockedUris" xml:space="preserve">
|
||||
<value>Auto-fill blocked URIs</value>
|
||||
</data>
|
||||
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
|
||||
<value>Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".</value>
|
||||
</data>
|
||||
<data name="AskToAddLogin" xml:space="preserve">
|
||||
<value>Ask to add login</value>
|
||||
</data>
|
||||
@@ -2678,5 +2675,50 @@ Do you want to switch to this account?</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
|
||||
<value>Vault timeout action changed to log out</value>
|
||||
<data name="BlockAutoFill" xml:space="preserve">
|
||||
<value>Block auto-fill</value>
|
||||
</data>
|
||||
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
|
||||
<value>Auto-fill will not be offered for these URIs.</value>
|
||||
</data>
|
||||
<data name="NewBlockedURI" xml:space="preserve">
|
||||
<value>New blocked URI</value>
|
||||
</data>
|
||||
<data name="URISaved" xml:space="preserve">
|
||||
<value>URI saved</value>
|
||||
</data>
|
||||
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
|
||||
<value>Invalid format. Use https://, http://, or androidapp://</value>
|
||||
<comment>https://, http://, androidapp:// should not be translated</comment>
|
||||
</data>
|
||||
<data name="EditURI" xml:space="preserve">
|
||||
<value>Edit URI</value>
|
||||
</data>
|
||||
<data name="EnterURI" xml:space="preserve">
|
||||
<value>Enter URI</value>
|
||||
</data>
|
||||
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
|
||||
<value>Format: {0}. Separate multiple URIs with a comma.</value>
|
||||
</data>
|
||||
<data name="FormatX" xml:space="preserve">
|
||||
<value>Format: {0}</value>
|
||||
</data>
|
||||
<data name="InvalidURI" xml:space="preserve">
|
||||
<value>Invalid URI</value>
|
||||
</data>
|
||||
<data name="URISaved" xml:space="preserve">
|
||||
<value>URI saved</value>
|
||||
</data>
|
||||
<data name="URIRemoved" xml:space="preserve">
|
||||
<value>URI removed</value>
|
||||
</data>
|
||||
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
|
||||
<value>There are no blocked URIs</value>
|
||||
</data>
|
||||
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
|
||||
<value>The URI {0} is already blocked</value>
|
||||
</data>
|
||||
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
|
||||
<value>Cannot edit multiple URIs at once</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Bit.App.Services
|
||||
return false;
|
||||
};
|
||||
|
||||
return await _cryptoService.CompareAndUpdateKeyHashAsync(password, null);
|
||||
return await _cryptoService.CompareAndUpdatePasswordHashAsync(password, null);
|
||||
}
|
||||
|
||||
public async Task<bool> Enabled()
|
||||
|
||||
@@ -428,6 +428,22 @@
|
||||
<Setter Property="VerticalOptions"
|
||||
Value="CenterAndExpand" />
|
||||
</Style>
|
||||
<Style TargetType="Button"
|
||||
ApplyToDerivedTypes="True"
|
||||
Class="box-row-button-muted">
|
||||
<Setter Property="BackgroundColor"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderWidth"
|
||||
Value="0" />
|
||||
<Setter Property="Padding"
|
||||
Value="0" />
|
||||
<Setter Property="TextColor"
|
||||
Value="{DynamicResource MutedColor}" />
|
||||
<Setter Property="HorizontalOptions"
|
||||
Value="End" />
|
||||
<Setter Property="VerticalOptions"
|
||||
Value="CenterAndExpand" />
|
||||
</Style>
|
||||
<Style TargetType="Button"
|
||||
ApplyToDerivedTypes="True"
|
||||
Class="box-overlay">
|
||||
|
||||
29
src/App/Utilities/Prompts/ValidatablePromptConfig.cs
Normal file
29
src/App/Utilities/Prompts/ValidatablePromptConfig.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Utilities.Prompts
|
||||
{
|
||||
public class ValidatablePromptConfig
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Subtitle { get; set; }
|
||||
public string Text { get; set; }
|
||||
public Func<string, string> ValidateText { get; set; }
|
||||
public string ValueSubInfo { get; set; }
|
||||
public string OkButtonText { get; set; }
|
||||
public string CancelButtonText { get; set; }
|
||||
public string ThirdButtonText { get; set; }
|
||||
public bool NumericKeyboard { get; set; }
|
||||
}
|
||||
|
||||
public struct ValidatablePromptResponse
|
||||
{
|
||||
public ValidatablePromptResponse(string text, bool executeThirdAction)
|
||||
{
|
||||
Text = text;
|
||||
ExecuteThirdAction = executeThirdAction;
|
||||
}
|
||||
|
||||
public string Text { get; set; }
|
||||
public bool ExecuteThirdAction { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user