diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 57812aa82..cef8a7a2c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -71,6 +71,11 @@ jobs:
with:
nuget-version: 5.9.0
+ - name: Set up .NET
+ uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
+ with:
+ dotnet-version: '3.1.x'
+
- name: Set up MSBuild
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml
index 5e2d17fb9..c486d1535 100644
--- a/.github/workflows/version-auto-bump.yml
+++ b/.github/workflows/version-auto-bump.yml
@@ -32,14 +32,10 @@ jobs:
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump:
- name: "Version bump"
- runs-on: ubuntu-22.04
- needs:
- - setup
- steps:
- - name: Bump version to ${{ needs.setup.outputs.version_number }}
- uses: ./.github/workflows/version-bump.yml
- secrets:
- AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- with:
- version_number: ${{ needs.setup.outputs.version_number }}
+ name: Bump version to ${{ needs.setup.outputs.version_number }}
+ needs: setup
+ uses: ./.github/workflows/version-bump.yml
+ secrets:
+ AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
+ with:
+ version_number: ${{ needs.setup.outputs.version_number }}
diff --git a/crowdin.yml b/crowdin.yml
index 57dfe8ff7..45e3e516d 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -38,3 +38,15 @@ files:
pt-PT: pt-PT
en-GB: en-GB
en-IN: en-IN
+ - source: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/Localizable.strings"
+ dest: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/%original_file_name%"
+ translation: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization//%two_letters_code%.lproj/%original_file_name%"
+ update_option: update_as_unapproved
+ languages_mapping:
+ two_letters_code:
+ zh-CN: zh-Hans
+ zh-TW: zh-Hant
+ pt-BR: pt-BR
+ pt-PT: pt-PT
+ en-GB: en-GB
+ en-IN: en-IN
diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index c5f838ff8..8561fb4f1 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -233,6 +233,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml
index 77372ff73..b4658aa9d 100644
--- a/src/Android/Properties/AndroidManifest.xml
+++ b/src/Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/src/Android/Renderers/CustomLabelRenderer.cs b/src/Android/Renderers/CustomLabelRenderer.cs
index 62287087d..838b9b967 100644
--- a/src/Android/Renderers/CustomLabelRenderer.cs
+++ b/src/Android/Renderers/CustomLabelRenderer.cs
@@ -1,10 +1,10 @@
-using System;
-using Bit.App.Controls;
-using System.ComponentModel;
-using Xamarin.Forms.Platform.Android;
+using System.ComponentModel;
using Android.Content;
-using Xamarin.Forms;
+using Android.OS;
+using Bit.App.Controls;
using Bit.Droid.Renderers;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Bit.Droid.Renderers
@@ -15,6 +15,19 @@ namespace Bit.Droid.Renderers
: base(context)
{ }
+ protected override void OnElementChanged(ElementChangedEventArgs
@@ -442,5 +443,6 @@
+
diff --git a/src/App/Controls/CustomLabel.cs b/src/App/Controls/CustomLabel.cs
index e822d3304..77d1fda79 100644
--- a/src/App/Controls/CustomLabel.cs
+++ b/src/App/Controls/CustomLabel.cs
@@ -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; }
}
}
-
diff --git a/src/App/Pages/Accounts/LockPage.xaml b/src/App/Pages/Accounts/LockPage.xaml
index f34a8b284..dabcb9fd6 100644
--- a/src/App/Pages/Accounts/LockPage.xaml
+++ b/src/App/Pages/Accounts/LockPage.xaml
@@ -46,7 +46,7 @@
@@ -89,7 +89,7 @@
diff --git a/src/App/Pages/Accounts/LockPage.xaml.cs b/src/App/Pages/Accounts/LockPage.xaml.cs
index f5a7914a5..d62b4c6b2 100644
--- a/src/App/Pages/Accounts/LockPage.xaml.cs
+++ b/src/App/Pages/Accounts/LockPage.xaml.cs
@@ -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;
diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs
index 26d15c630..dc6221e2e 100644
--- a/src/App/Pages/Accounts/LockPageViewModel.cs
+++ b/src/App/Pages/Accounts/LockPageViewModel.cs
@@ -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();
diff --git a/src/App/Pages/Accounts/RegisterPageViewModel.cs b/src/App/Pages/Accounts/RegisterPageViewModel.cs
index ebfc522c1..b742a65ba 100644
--- a/src/App/Pages/Accounts/RegisterPageViewModel.cs
+++ b/src/App/Pages/Accounts/RegisterPageViewModel.cs
@@ -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,
};
diff --git a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs
index 51d468275..57ffaac79 100644
--- a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs
+++ b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs
@@ -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 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
{
diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs b/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs
index daf4b2dbe..7d8ec85f0 100644
--- a/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs
+++ b/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs
@@ -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();
diff --git a/src/App/Pages/Settings/BlockAutofillUrisPage.xaml b/src/App/Pages/Settings/BlockAutofillUrisPage.xaml
new file mode 100644
index 000000000..8514edcad
--- /dev/null
+++ b/src/App/Pages/Settings/BlockAutofillUrisPage.xaml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App/Pages/Settings/BlockAutofillUrisPage.xaml.cs b/src/App/Pages/Settings/BlockAutofillUrisPage.xaml.cs
new file mode 100644
index 000000000..e8bf3b0cd
--- /dev/null
+++ b/src/App/Pages/Settings/BlockAutofillUrisPage.xaml.cs
@@ -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"));
+ }
+ }
+}
diff --git a/src/App/Pages/Settings/BlockAutofillUrisPageViewModel.cs b/src/App/Pages/Settings/BlockAutofillUrisPageViewModel.cs
new file mode 100644
index 000000000..a3f823990
--- /dev/null
+++ b/src/App/Pages/Settings/BlockAutofillUrisPageViewModel.cs
@@ -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();
+ _deviceActionService = ServiceContainer.Resolve();
+
+ AddUriCommand = new AsyncCommand(AddUriAsync,
+ onException: ex => HandleException(ex),
+ allowsMultipleExecutions: false);
+
+ EditUriCommand = new AsyncCommand(EditUriAsync,
+ onException: ex => HandleException(ex),
+ allowsMultipleExecutions: false);
+ }
+
+ public ObservableRangeCollection BlockedUris { get; set; } = new ObservableRangeCollection();
+
+ 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(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(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; }
+ }
+}
diff --git a/src/App/Pages/Settings/OptionsPage.xaml b/src/App/Pages/Settings/OptionsPage.xaml
index 8a6a407b5..39901574d 100644
--- a/src/App/Pages/Settings/OptionsPage.xaml
+++ b/src/App/Pages/Settings/OptionsPage.xaml
@@ -153,22 +153,14 @@
StyleClass="box-footer-label, box-footer-label-switch" />
-
-
-
-
+
+
+
+
diff --git a/src/App/Pages/Settings/OptionsPage.xaml.cs b/src/App/Pages/Settings/OptionsPage.xaml.cs
index 6cb54d21d..63c02605c 100644
--- a/src/App/Pages/Settings/OptionsPage.xaml.cs
+++ b/src/App/Pages/Settings/OptionsPage.xaml.cs
@@ -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())
diff --git a/src/App/Pages/Settings/OptionsPageViewModel.cs b/src/App/Pages/Settings/OptionsPageViewModel.cs
index ea36aad9c..8e0cbbc1d 100644
--- a/src/App/Pages/Settings/OptionsPageViewModel.cs
+++ b/src/App/Pages/Settings/OptionsPageViewModel.cs
@@ -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(null, AppResources.DefaultSystem)
};
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
+
+ GoToBlockAutofillUrisCommand = new AsyncCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()),
+ onException: ex => HandleException(ex),
+ allowsMultipleExecutions: false);
}
public List> 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();
- 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)
diff --git a/src/App/Pages/Vault/AttachmentsPageViewModel.cs b/src/App/Pages/Vault/AttachmentsPageViewModel.cs
index 02e9b2ae6..2e4b99b34 100644
--- a/src/App/Pages/Vault/AttachmentsPageViewModel.cs
+++ b/src/App/Pages/Vault/AttachmentsPageViewModel.cs
@@ -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)
diff --git a/src/App/Pages/Vault/CipherDetailsPage.xaml b/src/App/Pages/Vault/CipherDetailsPage.xaml
index dc1d6bdf0..f99196a6f 100644
--- a/src/App/Pages/Vault/CipherDetailsPage.xaml
+++ b/src/App/Pages/Vault/CipherDetailsPage.xaml
@@ -179,7 +179,7 @@
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding Cipher.ViewPassword}"
- AutomationId="ViewValueButton" />
+ AutomationId="ShowValueButton" />
{
- // 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 { 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 { 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();
+ });
}
}
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index aab53c53c..9c83af8de 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -823,15 +823,6 @@ namespace Bit.App.Resources {
}
}
- ///
- /// 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"..
- ///
- public static string AutofillBlockedUrisDescription {
- get {
- return ResourceManager.GetString("AutofillBlockedUrisDescription", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to Do you want to auto-fill or view this item?.
///
@@ -967,6 +958,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Auto-fill will not be offered for these URIs..
+ ///
+ public static string AutoFillWillNotBeOfferedForTheseURIs {
+ get {
+ return ResourceManager.GetString("AutoFillWillNotBeOfferedForTheseURIs", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Auto-fill with Bitwarden.
///
@@ -1255,6 +1255,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Block auto-fill.
+ ///
+ public static string BlockAutoFill {
+ get {
+ return ResourceManager.GetString("BlockAutoFill", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Brand.
///
@@ -1291,6 +1300,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Cannot edit multiple URIs at once.
+ ///
+ public static string CannotEditMultipleURIsAtOnce {
+ get {
+ return ResourceManager.GetString("CannotEditMultipleURIsAtOnce", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Cannot open the app "{0}"..
///
@@ -2173,6 +2191,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Edit URI.
+ ///
+ public static string EditURI {
+ get {
+ return ResourceManager.GetString("EditURI", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Email.
///
@@ -2326,6 +2353,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Enter URI.
+ ///
+ public static string EnterURI {
+ get {
+ return ResourceManager.GetString("EnterURI", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Enter the 6 digit verification code from your authenticator app..
///
@@ -2974,6 +3010,24 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Format: {0}.
+ ///
+ public static string FormatX {
+ get {
+ return ResourceManager.GetString("FormatX", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Format: {0}. Separate multiple URIs with a comma..
+ ///
+ public static string FormatXSeparateMultipleURIsWithAComma {
+ get {
+ return ResourceManager.GetString("FormatXSeparateMultipleURIsWithAComma", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Forwarded email alias.
///
@@ -3316,6 +3370,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Invalid format. Use https://, http://, or androidapp://.
+ ///
+ public static string InvalidFormatUseHttpsHttpOrAndroidApp {
+ get {
+ return ResourceManager.GetString("InvalidFormatUseHttpsHttpOrAndroidApp", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Invalid master password. Try again..
///
@@ -3334,6 +3397,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Invalid URI.
+ ///
+ public static string InvalidURI {
+ get {
+ return ResourceManager.GetString("InvalidURI", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Invalid verification code.
///
@@ -4263,6 +4335,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to New blocked URI.
+ ///
+ public static string NewBlockedURI {
+ get {
+ return ResourceManager.GetString("NewBlockedURI", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to New custom field.
///
@@ -6182,6 +6263,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to There are no blocked URIs.
+ ///
+ public static string ThereAreNoBlockedURIs {
+ get {
+ return ResourceManager.GetString("ThereAreNoBlockedURIs", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to There are no items in your vault that match "{0}".
///
@@ -6200,6 +6290,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to The URI {0} is already blocked.
+ ///
+ public static string TheURIXIsAlreadyBlocked {
+ get {
+ return ResourceManager.GetString("TheURIXIsAlreadyBlocked", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to 30 days.
///
@@ -6668,6 +6767,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to URI removed.
+ ///
+ public static string URIRemoved {
+ get {
+ return ResourceManager.GetString("URIRemoved", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to URIs.
///
@@ -6677,6 +6785,15 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to URI saved.
+ ///
+ public static string URISaved {
+ get {
+ return ResourceManager.GetString("URISaved", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to US.
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index 3076da1c9..f7c1be34b 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -1585,9 +1585,6 @@ Scanning will happen automatically.
Auto-fill blocked URIs
-
- Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".
-
Ask to add login
@@ -2678,5 +2675,50 @@ Do you want to switch to this account?
Vault timeout action changed to log out
+
+ Block auto-fill
+
+
+ Auto-fill will not be offered for these URIs.
+
+
+ New blocked URI
+
+
+ URI saved
+
+
+ Invalid format. Use https://, http://, or androidapp://
+ https://, http://, androidapp:// should not be translated
+
+
+ Edit URI
+
+
+ Enter URI
+
+
+ Format: {0}. Separate multiple URIs with a comma.
+
+
+ Format: {0}
+
+
+ Invalid URI
+
+
+ URI saved
+
+
+ URI removed
+
+
+ There are no blocked URIs
+
+
+ The URI {0} is already blocked
+
+
+ Cannot edit multiple URIs at once
diff --git a/src/App/Services/MobilePasswordRepromptService.cs b/src/App/Services/MobilePasswordRepromptService.cs
index 28a8e5a86..460e9df58 100644
--- a/src/App/Services/MobilePasswordRepromptService.cs
+++ b/src/App/Services/MobilePasswordRepromptService.cs
@@ -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 Enabled()
diff --git a/src/App/Styles/Base.xaml b/src/App/Styles/Base.xaml
index 1d74ba518..2dac11bf7 100644
--- a/src/App/Styles/Base.xaml
+++ b/src/App/Styles/Base.xaml
@@ -428,6 +428,22 @@
+