mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc350cf20c | ||
|
|
8f59e79fac | ||
|
|
0b51796b63 | ||
|
|
3bcb44ea71 | ||
|
|
b108b4e71d | ||
|
|
a72f267558 | ||
|
|
cc75cebdb8 | ||
|
|
3a0510d6b4 | ||
|
|
0c4b88e562 | ||
|
|
ac3b0c2bad |
20
.github/workflows/build.yml
vendored
20
.github/workflows/build.yml
vendored
@@ -191,7 +191,7 @@ jobs:
|
||||
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
||||
$packageName = "com.x8bit.bitwarden";
|
||||
|
||||
if ("${{ matrix.variant }}" -ne "prod")
|
||||
if ("${{ matrix.variant }}" -ne "prod")
|
||||
{
|
||||
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
|
||||
}
|
||||
@@ -519,15 +519,15 @@ jobs:
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Login to Azure - Prod Subscription
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
env:
|
||||
KEYVAULT: bitwarden-prod-kv
|
||||
KEYVAULT: bitwarden-ci
|
||||
SECRETS: |
|
||||
appcenter-ios-token
|
||||
run: |
|
||||
@@ -773,15 +773,15 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
|
||||
- name: Login to Azure
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
env:
|
||||
KEYVAULT: bitwarden-prod-kv
|
||||
KEYVAULT: bitwarden-ci
|
||||
SECRETS: |
|
||||
crowdin-api-token
|
||||
run: |
|
||||
@@ -839,17 +839,17 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Login to Azure - Prod Subscription
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||
if: failure()
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
if: failure()
|
||||
env:
|
||||
KEYVAULT: bitwarden-prod-kv
|
||||
KEYVAULT: bitwarden-ci
|
||||
SECRETS: |
|
||||
devops-alerts-slack-webhook-url
|
||||
run: |
|
||||
|
||||
10
.github/workflows/crowdin-pull.yml
vendored
10
.github/workflows/crowdin-pull.yml
vendored
@@ -17,16 +17,16 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||
|
||||
- name: Login to Azure
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||
|
||||
- name: Download translations
|
||||
@@ -48,4 +48,4 @@ jobs:
|
||||
pull_request_title: "Autosync Crowdin Translations"
|
||||
pull_request_body: "Autosync the updated translations"
|
||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
|
||||
2
.github/workflows/version-auto-bump.yml
vendored
2
.github/workflows/version-auto-bump.yml
vendored
@@ -40,6 +40,6 @@ jobs:
|
||||
- name: Bump version to ${{ needs.setup.outputs.version_number }}
|
||||
uses: ./.github/workflows/version-bump.yml
|
||||
secrets:
|
||||
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
with:
|
||||
version_number: ${{ needs.setup.outputs.version_number }}
|
||||
|
||||
7
.github/workflows/version-bump.yml
vendored
7
.github/workflows/version-bump.yml
vendored
@@ -11,6 +11,7 @@ on:
|
||||
inputs:
|
||||
version_number:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
AZURE_PROD_KV_CREDENTIALS:
|
||||
required: true
|
||||
@@ -23,16 +24,16 @@ jobs:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
|
||||
- name: Login to Azure - Prod Subscription
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||
|
||||
- name: Import GPG key
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.3.3" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.4.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
@@ -161,6 +161,14 @@ namespace Bit.App
|
||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||
});
|
||||
}
|
||||
else if (message.Command == Constants.ForceUpdatePassword)
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
await Application.Current.MainPage.Navigation.PushModalAsync(
|
||||
new NavigationPage(new UpdateTempPasswordPage()));
|
||||
});
|
||||
}
|
||||
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
||||
|| message.Command == "unlocked"
|
||||
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Domain;
|
||||
@@ -73,13 +70,13 @@ namespace Bit.App.Pages
|
||||
set => SetProperty(ref _policy, value);
|
||||
}
|
||||
|
||||
public string ShowPasswordIcon => ShowPassword ? "" : "";
|
||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||
public string MasterPassword { get; set; }
|
||||
public string ConfirmMasterPassword { get; set; }
|
||||
public string Hint { get; set; }
|
||||
|
||||
public async Task InitAsync(bool forceSync = false)
|
||||
public virtual async Task InitAsync(bool forceSync = false)
|
||||
{
|
||||
if (forceSync)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace Bit.App.Pages
|
||||
private readonly ILogger _logger;
|
||||
private readonly IWatchDeviceService _watchDeviceService;
|
||||
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
|
||||
private string _email;
|
||||
private string _masterPassword;
|
||||
@@ -61,6 +63,8 @@ namespace Bit.App.Pages
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
||||
|
||||
PageTitle = AppResources.VerifyMasterPassword;
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
@@ -294,6 +298,7 @@ namespace Bit.App.Pages
|
||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
|
||||
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||
var passwordValid = false;
|
||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||
|
||||
if (storedKeyHash != null)
|
||||
{
|
||||
@@ -305,9 +310,11 @@ namespace Bit.App.Pages
|
||||
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||
var request = new PasswordVerificationRequest();
|
||||
request.MasterPasswordHash = keyHash;
|
||||
|
||||
try
|
||||
{
|
||||
await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
||||
passwordValid = true;
|
||||
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||
@@ -328,6 +335,14 @@ namespace Bit.App.Pages
|
||||
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
|
||||
await _stateService.SetForcePasswordResetReasonAsync(
|
||||
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
|
||||
}
|
||||
|
||||
MasterPassword = string.Empty;
|
||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||
await SetKeyAndContinueAsync(key);
|
||||
@@ -352,6 +367,37 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the master password requires updating to meet the enforced policy requirements
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
private async Task<bool> RequirePasswordChangeAsync(MasterPasswordPolicyOptions options = null)
|
||||
{
|
||||
// If no policy options are provided, attempt to load them from the policy service
|
||||
var enforcedOptions = options ?? await _policyService.GetMasterPasswordPolicyOptions();
|
||||
|
||||
// No policy to enforce on login/unlock
|
||||
if (!(enforcedOptions is { EnforceOnLogin: true }))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var strength = _passwordGenerationService.PasswordStrength(
|
||||
MasterPassword, _passwordGenerationService.GetPasswordStrengthUserInput(_email))?.Score;
|
||||
|
||||
if (!strength.HasValue)
|
||||
{
|
||||
_logger.Error("Unable to evaluate master password strength during unlock");
|
||||
return false;
|
||||
}
|
||||
|
||||
return !await _policyService.EvaluateMasterPassword(
|
||||
strength.Value,
|
||||
MasterPassword,
|
||||
enforcedOptions
|
||||
);
|
||||
}
|
||||
|
||||
public async Task LogOutAsync()
|
||||
{
|
||||
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LogoutConfirmation,
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
BackgroundColor="Transparent"
|
||||
BorderColor="{DynamicResource PrimaryColor}">
|
||||
<Label
|
||||
Text="{u:I18n UpdateMasterPasswordWarning}"
|
||||
Text="{Binding UpdateMasterPasswordWarningText }"
|
||||
StyleClass="text-muted, text-sm, text-bold"
|
||||
HorizontalTextAlignment="Center" />
|
||||
</Frame>
|
||||
@@ -74,6 +74,40 @@
|
||||
HorizontalTextAlignment="Start" />
|
||||
</Frame>
|
||||
</Grid>
|
||||
<Grid StyleClass="box-row" IsVisible="{Binding RequireCurrentPassword }">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Text="{u:I18n CurrentMasterPassword}"
|
||||
StyleClass="box-label"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0" />
|
||||
<controls:MonoEntry
|
||||
x:Name="_currentMasterPassword"
|
||||
Text="{Binding CurrentMasterPassword}"
|
||||
StyleClass="box-value"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsTextPredictionEnabled="False"
|
||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0" />
|
||||
<controls:IconButton
|
||||
StyleClass="box-row-button, box-row-button-platform"
|
||||
Text="{Binding ShowPasswordIcon}"
|
||||
Command="{Binding TogglePasswordCommand}"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.RowSpan="2"
|
||||
AutomationProperties.IsInAccessibleTree="True"
|
||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||
</Grid>
|
||||
<Grid StyleClass="box-row">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
@@ -1,27 +1,68 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Bit.App.Pages
|
||||
{
|
||||
public class UpdateTempPasswordPageViewModel : BaseChangePasswordViewModel
|
||||
{
|
||||
private readonly IUserVerificationService _userVerificationService;
|
||||
|
||||
private ForcePasswordResetReason _reason = ForcePasswordResetReason.AdminForcePasswordReset;
|
||||
|
||||
public UpdateTempPasswordPageViewModel()
|
||||
{
|
||||
PageTitle = AppResources.UpdateMasterPassword;
|
||||
TogglePasswordCommand = new Command(TogglePassword);
|
||||
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
|
||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||
SubmitCommand = new AsyncCommand(SubmitAsync,
|
||||
onException: ex => HandleException(ex),
|
||||
allowsMultipleExecutions: false);
|
||||
|
||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
||||
}
|
||||
|
||||
public Command SubmitCommand { get; }
|
||||
public AsyncCommand SubmitCommand { get; }
|
||||
public Command TogglePasswordCommand { get; }
|
||||
public Command ToggleConfirmPasswordCommand { get; }
|
||||
public Action UpdateTempPasswordSuccessAction { get; set; }
|
||||
public Action LogOutAction { get; set; }
|
||||
public string CurrentMasterPassword { get; set; }
|
||||
|
||||
public override async Task InitAsync(bool forceSync = false)
|
||||
{
|
||||
await base.InitAsync(forceSync);
|
||||
|
||||
var forcePasswordResetReason = await _stateService.GetForcePasswordResetReasonAsync();
|
||||
|
||||
if (forcePasswordResetReason.HasValue)
|
||||
{
|
||||
_reason = forcePasswordResetReason.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool RequireCurrentPassword
|
||||
{
|
||||
get => _reason == ForcePasswordResetReason.WeakMasterPasswordOnLogin;
|
||||
}
|
||||
|
||||
public string UpdateMasterPasswordWarningText
|
||||
{
|
||||
get
|
||||
{
|
||||
return _reason == ForcePasswordResetReason.WeakMasterPasswordOnLogin
|
||||
? AppResources.UpdateWeakMasterPasswordWarning
|
||||
: AppResources.UpdateMasterPasswordWarning;
|
||||
}
|
||||
}
|
||||
|
||||
public void TogglePassword()
|
||||
{
|
||||
@@ -42,6 +83,12 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
|
||||
if (RequireCurrentPassword &&
|
||||
!await _userVerificationService.VerifyUser(CurrentMasterPassword, VerificationType.MasterPassword))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve details for key generation
|
||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||
var email = await _stateService.GetEmailAsync();
|
||||
@@ -53,21 +100,29 @@ namespace Bit.App.Pages
|
||||
// Create new encKey for the User
|
||||
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||
|
||||
// Create request
|
||||
var request = new UpdateTempPasswordRequest
|
||||
{
|
||||
Key = newEncKey.Item2.EncryptedString,
|
||||
NewMasterPasswordHash = masterPasswordHash,
|
||||
MasterPasswordHint = Hint
|
||||
};
|
||||
|
||||
// Initiate API action
|
||||
try
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.UpdatingPassword);
|
||||
await _apiService.PutUpdateTempPasswordAsync(request);
|
||||
|
||||
switch (_reason)
|
||||
{
|
||||
case ForcePasswordResetReason.AdminForcePasswordReset:
|
||||
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||
break;
|
||||
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
||||
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
await _deviceActionService.HideLoadingAsync();
|
||||
|
||||
// Clear the force reset password reason
|
||||
await _stateService.SetForcePasswordResetReasonAsync(null);
|
||||
|
||||
_platformUtilsService.ShowToast(null, null, AppResources.UpdatedMasterPassword);
|
||||
|
||||
UpdateTempPasswordSuccessAction?.Invoke();
|
||||
}
|
||||
catch (ApiException e)
|
||||
@@ -85,5 +140,32 @@ namespace Bit.App.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateTempPasswordAsync(string newMasterPasswordHash, string newEncKey)
|
||||
{
|
||||
var request = new UpdateTempPasswordRequest
|
||||
{
|
||||
Key = newEncKey,
|
||||
NewMasterPasswordHash = newMasterPasswordHash,
|
||||
MasterPasswordHint = Hint
|
||||
};
|
||||
|
||||
await _apiService.PutUpdateTempPasswordAsync(request);
|
||||
}
|
||||
|
||||
private async Task UpdatePasswordAsync(string newMasterPasswordHash, string newEncKey)
|
||||
{
|
||||
var currentPasswordHash = await _cryptoService.HashPasswordAsync(CurrentMasterPassword, null);
|
||||
|
||||
var request = new PasswordRequest
|
||||
{
|
||||
MasterPasswordHash = currentPasswordHash,
|
||||
Key = newEncKey,
|
||||
NewMasterPasswordHash = newMasterPasswordHash,
|
||||
MasterPasswordHint = Hint
|
||||
};
|
||||
|
||||
await _apiService.PostPasswordAsync(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,7 @@ using Bit.App.Pages.Accounts;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
@@ -51,7 +48,7 @@ namespace Bit.App.Pages
|
||||
private bool _reportLoggingEnabled;
|
||||
private bool _approvePasswordlessLoginRequests;
|
||||
private bool _shouldConnectToWatch;
|
||||
private List<KeyValuePair<string, int?>> _vaultTimeouts =
|
||||
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
|
||||
new List<KeyValuePair<string, int?>>
|
||||
{
|
||||
new KeyValuePair<string, int?>(AppResources.Immediately, 0),
|
||||
@@ -65,7 +62,7 @@ namespace Bit.App.Pages
|
||||
new KeyValuePair<string, int?>(AppResources.Never, null),
|
||||
new KeyValuePair<string, int?>(AppResources.Custom, CustomVaultTimeoutValue),
|
||||
};
|
||||
private List<KeyValuePair<string, VaultTimeoutAction>> _vaultTimeoutActions =
|
||||
private readonly static List<KeyValuePair<string, VaultTimeoutAction>> VaultTimeoutActionOptions =
|
||||
new List<KeyValuePair<string, VaultTimeoutAction>>
|
||||
{
|
||||
new KeyValuePair<string, VaultTimeoutAction>(AppResources.Lock, VaultTimeoutAction.Lock),
|
||||
@@ -74,6 +71,8 @@ namespace Bit.App.Pages
|
||||
|
||||
private Policy _vaultTimeoutPolicy;
|
||||
private int? _vaultTimeout;
|
||||
private List<KeyValuePair<string, int?>> _vaultTimeoutOptions = VaultTimeoutOptions;
|
||||
private List<KeyValuePair<string, VaultTimeoutAction>> _vaultTimeoutActionOptions = VaultTimeoutActionOptions;
|
||||
|
||||
public SettingsPageViewModel()
|
||||
{
|
||||
@@ -117,20 +116,28 @@ namespace Bit.App.Pages
|
||||
_localizeService.GetLocaleShortTime(lastSync.Value));
|
||||
}
|
||||
|
||||
_vaultTimeoutPolicy = null;
|
||||
_vaultTimeoutOptions = VaultTimeoutOptions;
|
||||
_vaultTimeoutActionOptions = VaultTimeoutActionOptions;
|
||||
|
||||
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
|
||||
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
||||
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
|
||||
|
||||
var action = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
|
||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
|
||||
|
||||
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
|
||||
{
|
||||
// if we have a vault timeout policy, we need to filter the timeout options
|
||||
_vaultTimeoutPolicy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout)).First();
|
||||
var minutes = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes").GetValueOrDefault();
|
||||
_vaultTimeouts = _vaultTimeouts.Where(t =>
|
||||
t.Value <= minutes &&
|
||||
var policyMinutes = _policyService.GetPolicyInt(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_MINUTES);
|
||||
_vaultTimeoutOptions = _vaultTimeoutOptions.Where(t =>
|
||||
t.Value <= policyMinutes &&
|
||||
(t.Value > 0 || t.Value == CustomVaultTimeoutValue) &&
|
||||
t.Value != null).ToList();
|
||||
}
|
||||
|
||||
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
|
||||
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
||||
var action = await _stateService.GetVaultTimeoutActionAsync() ?? VaultTimeoutAction.Lock;
|
||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
|
||||
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||
_pin = pinSet.Item1 || pinSet.Item2;
|
||||
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
||||
@@ -266,7 +273,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
var oldTimeout = _vaultTimeout;
|
||||
|
||||
var options = _vaultTimeouts.Select(
|
||||
var options = _vaultTimeoutOptions.Select(
|
||||
o => o.Key == _vaultTimeoutDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
|
||||
if (promptOptions)
|
||||
{
|
||||
@@ -277,7 +284,7 @@ namespace Bit.App.Pages
|
||||
return;
|
||||
}
|
||||
var cleanSelection = selection.Replace("✓ ", string.Empty);
|
||||
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
|
||||
var selectionOption = _vaultTimeoutOptions.FirstOrDefault(o => o.Key == cleanSelection);
|
||||
|
||||
// Check if the selected Timeout action is "Never" and if it's different from the previous selected value
|
||||
if (selectionOption.Value == null && selectionOption.Value != oldTimeout)
|
||||
@@ -295,13 +302,13 @@ namespace Bit.App.Pages
|
||||
|
||||
if (_vaultTimeoutPolicy != null)
|
||||
{
|
||||
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes");
|
||||
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_MINUTES);
|
||||
|
||||
if (newTimeout > maximumTimeout)
|
||||
{
|
||||
await _platformUtilsService.ShowDialogAsync(AppResources.VaultTimeoutToLarge, AppResources.Warning);
|
||||
var timeout = await _vaultTimeoutService.GetVaultTimeout();
|
||||
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == timeout).Key ??
|
||||
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == timeout).Key ??
|
||||
AppResources.Custom;
|
||||
return;
|
||||
}
|
||||
@@ -374,7 +381,13 @@ namespace Bit.App.Pages
|
||||
|
||||
public async Task VaultTimeoutActionAsync()
|
||||
{
|
||||
var options = _vaultTimeoutActions.Select(o =>
|
||||
if (_vaultTimeoutPolicy != null &&
|
||||
!string.IsNullOrEmpty(_policyService.GetPolicyString(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_ACTION)))
|
||||
{
|
||||
// do nothing if we have a policy set
|
||||
return;
|
||||
}
|
||||
var options = _vaultTimeoutActionOptions.Select(o =>
|
||||
o.Key == _vaultTimeoutActionDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
|
||||
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
|
||||
AppResources.Cancel, null, options);
|
||||
@@ -393,7 +406,7 @@ namespace Bit.App.Pages
|
||||
cleanSelection = AppResources.Lock;
|
||||
}
|
||||
}
|
||||
var selectionOption = _vaultTimeoutActions.FirstOrDefault(o => o.Key == cleanSelection);
|
||||
var selectionOption = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Key == cleanSelection);
|
||||
var changed = _vaultTimeoutActionDisplayValue != selectionOption.Key;
|
||||
_vaultTimeoutActionDisplayValue = selectionOption.Key;
|
||||
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout,
|
||||
@@ -597,14 +610,36 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (_vaultTimeoutPolicy != null)
|
||||
{
|
||||
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes").GetValueOrDefault();
|
||||
securityItems.Insert(0, new SettingsPageListItem
|
||||
var policyMinutes = _policyService.GetPolicyInt(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_MINUTES);
|
||||
var policyAction = _policyService.GetPolicyString(_vaultTimeoutPolicy, PolicyService.TIMEOUT_POLICY_ACTION);
|
||||
|
||||
if (policyMinutes.HasValue || !string.IsNullOrWhiteSpace(policyAction))
|
||||
{
|
||||
Name = string.Format(AppResources.VaultTimeoutPolicyInEffect,
|
||||
Math.Floor((float)maximumTimeout / 60),
|
||||
maximumTimeout % 60),
|
||||
UseFrame = true,
|
||||
});
|
||||
string policyAlert;
|
||||
if (policyMinutes.HasValue && string.IsNullOrWhiteSpace(policyAction))
|
||||
{
|
||||
policyAlert = string.Format(AppResources.VaultTimeoutPolicyInEffect,
|
||||
Math.Floor((float)policyMinutes / 60),
|
||||
policyMinutes % 60);
|
||||
}
|
||||
else if (!policyMinutes.HasValue && !string.IsNullOrWhiteSpace(policyAction))
|
||||
{
|
||||
policyAlert = string.Format(AppResources.VaultTimeoutActionPolicyInEffect,
|
||||
policyAction == PolicyService.TIMEOUT_POLICY_ACTION_LOCK ? AppResources.Lock : AppResources.LogOut);
|
||||
}
|
||||
else
|
||||
{
|
||||
policyAlert = string.Format(AppResources.VaultTimeoutPolicyWithActionInEffect,
|
||||
Math.Floor((float)policyMinutes / 60),
|
||||
policyMinutes % 60,
|
||||
policyAction == PolicyService.TIMEOUT_POLICY_ACTION_LOCK ? AppResources.Lock : AppResources.LogOut);
|
||||
}
|
||||
securityItems.Insert(0, new SettingsPageListItem
|
||||
{
|
||||
Name = policyAlert,
|
||||
UseFrame = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (Device.RuntimePlatform == Device.Android)
|
||||
{
|
||||
@@ -792,12 +827,12 @@ namespace Bit.App.Pages
|
||||
|
||||
private VaultTimeoutAction GetVaultTimeoutActionFromKey(string key)
|
||||
{
|
||||
return _vaultTimeoutActions.FirstOrDefault(o => o.Key == key).Value;
|
||||
return _vaultTimeoutActionOptions.FirstOrDefault(o => o.Key == key).Value;
|
||||
}
|
||||
|
||||
private int? GetVaultTimeoutFromKey(string key)
|
||||
{
|
||||
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
|
||||
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
|
||||
}
|
||||
|
||||
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||
using Bit.App.Effects;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Utilities;
|
||||
@@ -15,6 +16,7 @@ namespace Bit.App.Pages
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
|
||||
private NavigationPage _groupingsPage;
|
||||
@@ -26,6 +28,7 @@ namespace Bit.App.Pages
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||
|
||||
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage))
|
||||
{
|
||||
@@ -95,6 +98,13 @@ namespace Bit.App.Pages
|
||||
{
|
||||
_messagingService.Send("convertAccountToKeyConnector");
|
||||
}
|
||||
|
||||
var forcePasswordResetReason = await _stateService.GetForcePasswordResetReasonAsync();
|
||||
|
||||
if (forcePasswordResetReason.HasValue)
|
||||
{
|
||||
_messagingService.Send(Constants.ForceUpdatePassword);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisappearing()
|
||||
|
||||
38
src/App/Resources/AppResources.Designer.cs
generated
38
src/App/Resources/AppResources.Designer.cs
generated
@@ -1687,6 +1687,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Current master password.
|
||||
/// </summary>
|
||||
public static string CurrentMasterPassword {
|
||||
get {
|
||||
return ResourceManager.GetString("CurrentMasterPassword", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom.
|
||||
/// </summary>
|
||||
@@ -6452,6 +6461,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour..
|
||||
/// </summary>
|
||||
public static string UpdateWeakMasterPasswordWarning {
|
||||
get {
|
||||
return ResourceManager.GetString("UpdateWeakMasterPasswordWarning", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Updating password.
|
||||
/// </summary>
|
||||
@@ -6677,6 +6695,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
|
||||
/// </summary>
|
||||
public static string VaultTimeoutActionPolicyInEffect {
|
||||
get {
|
||||
return ResourceManager.GetString("VaultTimeoutActionPolicyInEffect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?.
|
||||
/// </summary>
|
||||
@@ -6687,7 +6714,7 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is {0} hour(s) and {1} minute(s).
|
||||
/// Looks up a localized string similar to Your organization policies have set your maximum allowed vault timeout to {0} hour(s) and {1} minute(s)..
|
||||
/// </summary>
|
||||
public static string VaultTimeoutPolicyInEffect {
|
||||
get {
|
||||
@@ -6695,6 +6722,15 @@ namespace Bit.App.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is {0} hour(s) and {1} minute(s). Your vault timeout action is set to {2}..
|
||||
/// </summary>
|
||||
public static string VaultTimeoutPolicyWithActionInEffect {
|
||||
get {
|
||||
return ResourceManager.GetString("VaultTimeoutPolicyWithActionInEffect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Your vault timeout exceeds the restrictions set by your organization..
|
||||
/// </summary>
|
||||
|
||||
@@ -125,14 +125,14 @@
|
||||
<comment>Add/create a new entity (verb).</comment>
|
||||
</data>
|
||||
<data name="AddFolder" xml:space="preserve">
|
||||
<value>नया फ़ोल्डर जोड़ें</value>
|
||||
<value>नया फोल्डर जोड़ें</value>
|
||||
</data>
|
||||
<data name="AddItem" xml:space="preserve">
|
||||
<value>आइटम जोड़ें</value>
|
||||
<value>चीज़ जोड़ें</value>
|
||||
<comment>The title for the add item page.</comment>
|
||||
</data>
|
||||
<data name="AnErrorHasOccurred" xml:space="preserve">
|
||||
<value>एक त्रुटि हुई।</value>
|
||||
<value>एक गड़बड़ हुई।</value>
|
||||
<comment>Alert title when something goes wrong.</comment>
|
||||
</data>
|
||||
<data name="Back" xml:space="preserve">
|
||||
|
||||
@@ -1052,7 +1052,7 @@
|
||||
<value>Dicembre</value>
|
||||
</data>
|
||||
<data name="Dr" xml:space="preserve">
|
||||
<value>Dott.</value>
|
||||
<value>Dott</value>
|
||||
</data>
|
||||
<data name="ExpirationMonth" xml:space="preserve">
|
||||
<value>Mese di scadenza</value>
|
||||
@@ -1094,7 +1094,7 @@
|
||||
<value>Secondo nome</value>
|
||||
</data>
|
||||
<data name="Mr" xml:space="preserve">
|
||||
<value>Sig.</value>
|
||||
<value>Sig</value>
|
||||
</data>
|
||||
<data name="Mrs" xml:space="preserve">
|
||||
<value>Sig.ra</value>
|
||||
@@ -1359,7 +1359,7 @@
|
||||
<value>Questa password è stata trovata {0} volta(e) in archivi di dati trafugati. Dovresti cambiarla.</value>
|
||||
</data>
|
||||
<data name="PasswordSafe" xml:space="preserve">
|
||||
<value>Questa password non è stata trovata in dati violati noti. Dovrebbe essere sicura da usare.</value>
|
||||
<value>Questa password non è stata trovata in violazioni di dati note. Dovrebbe essere sicura da usare.</value>
|
||||
</data>
|
||||
<data name="IdentityName" xml:space="preserve">
|
||||
<value>Nome dell'identità</value>
|
||||
@@ -1436,7 +1436,7 @@
|
||||
<value>Nessuna organizzazione da mostrare.</value>
|
||||
</data>
|
||||
<data name="MoveToOrgDesc" xml:space="preserve">
|
||||
<value>Scegli un'organizzazione in cui vuoi spostare questo elemento. Spostarlo in un'organizzazione trasferisce la proprietà dell'elemento all'organizzazione. Una volta spostato non sarai più il proprietario diretto di questo elemento.</value>
|
||||
<value>Scegli un'organizzazione in cui vuoi spostare questo elemento. Spostarlo in un'organizzazione trasferisce la proprietà dell'elemento all'organizzazione. Una volta spostato, non sarai più il proprietario diretto di questo elemento.</value>
|
||||
</data>
|
||||
<data name="NumberOfWords" xml:space="preserve">
|
||||
<value>Numero di parole</value>
|
||||
@@ -1667,10 +1667,10 @@
|
||||
<value>Conferma la tua identità per continuare.</value>
|
||||
</data>
|
||||
<data name="ExportVaultWarning" xml:space="preserve">
|
||||
<value>Questa esportazione contiene i dati della tua cassaforte in formato non criptato. Non archiviare o inviare il file di esportazione attraverso canali non sicuri (come email). Eliminalo immediatamente dopo che hai finito di usarli.</value>
|
||||
<value>Questa esportazione contiene i dati della tua cassaforte in un formato non criptato. Non salvare o inviare il file esportato attraverso canali non protetti (come le email). Eliminalo immediatamente dopo l'utilizzo.</value>
|
||||
</data>
|
||||
<data name="EncExportKeyWarning" xml:space="preserve">
|
||||
<value>Questa esportazione cripta i tuoi dati utilizzando la chiave di criptografia del tuo account. Se cambi la chiave di criptografia del tuo account, non potrai più decriptare il file esportato e dovrai creare un nuovo file di esportazione.</value>
|
||||
<value>Questa esportazione cripta i tuoi dati usando la chiave di criptografia del tuo account. Se cambi la chiave di criptografia del tuo account, non potrai più decifrare il file esportato e dovrai eseguire una nuova esportazione.</value>
|
||||
</data>
|
||||
<data name="EncExportAccountWarning" xml:space="preserve">
|
||||
<value>Le chiavi di criptografia dell'account sono uniche per ogni account Bitwarden, quindi non puoi importare un file di esportazione criptato in un account diverso.</value>
|
||||
@@ -1782,7 +1782,7 @@
|
||||
<value>Per completare l'accesso con SSO, imposta una password principale per accedere e proteggere la cassaforte.</value>
|
||||
</data>
|
||||
<data name="MasterPasswordPolicyInEffect" xml:space="preserve">
|
||||
<value>La password principale deve avere i seguenti requisiti, stabiliti dalle politiche dell'organizzazione:</value>
|
||||
<value>Una o più politiche dell'organizzazione richiedono che la tua password principale soddisfi questi requisiti:</value>
|
||||
</data>
|
||||
<data name="PolicyInEffectMinComplexity" xml:space="preserve">
|
||||
<value>Punteggio minimo di complessità {0}</value>
|
||||
@@ -1946,7 +1946,7 @@
|
||||
<value>Numero massimo di accessi</value>
|
||||
</data>
|
||||
<data name="MaximumAccessCountInfo" xml:space="preserve">
|
||||
<value>Se impostata, gli utenti potranno più accedere a questo Send una volta raggiunto il numero massimo di accessi.</value>
|
||||
<value>Se impostata, gli utenti non potranno più accedere a questo Send una volta raggiunto il numero massimo di accessi.</value>
|
||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="MaximumAccessCountReached" xml:space="preserve">
|
||||
@@ -2074,13 +2074,13 @@
|
||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||
</data>
|
||||
<data name="PasswordPrompt" xml:space="preserve">
|
||||
<value>Nuova richiesta della password principale</value>
|
||||
<value>Richiedi di inserire la password principale di nuovo per visualizzare questo elemento</value>
|
||||
</data>
|
||||
<data name="PasswordConfirmation" xml:space="preserve">
|
||||
<value>Conferma della password principale</value>
|
||||
</data>
|
||||
<data name="PasswordConfirmationDesc" xml:space="preserve">
|
||||
<value>Questa azione è protetta. Per continuare, digita nuovamente la tua password principale per verificare la tua identità.</value>
|
||||
<value>Questa azione è protetta. Inserisci la tua password principale di nuovo per verificare la tua identità.</value>
|
||||
</data>
|
||||
<data name="CaptchaRequired" xml:space="preserve">
|
||||
<value>Captcha obbligatorio</value>
|
||||
@@ -2095,7 +2095,7 @@
|
||||
<value>Aggiorna password principale</value>
|
||||
</data>
|
||||
<data name="UpdateMasterPasswordWarning" xml:space="preserve">
|
||||
<value>La tua password principale è stata recentemente modificata da un amministratore nella tua organizzazione. Per accedere alla cassaforte, devi aggiornarla ora. Procedendo sarai disconnesso dalla sessione attuale, richiedendo di effettuare nuovamente l'accesso. Le sessioni attive su altri dispositivi possono continuare a rimanere attive per un massimo di un'ora.</value>
|
||||
<value>La tua password principale è stata recentemente modificata da un amministratore nella tua organizzazione. Per accedere alla cassaforte, aggiornala ora. Procedere ti farà uscire dalla sessione corrente, richiedendoti di accedere di nuovo. Le sessioni attive su altri dispositivi potrebbero continuare a rimanere attive per un massimo di un'ora.</value>
|
||||
</data>
|
||||
<data name="UpdatingPassword" xml:space="preserve">
|
||||
<value>Aggiornamento password</value>
|
||||
|
||||
@@ -2141,7 +2141,13 @@ Scanning will happen automatically.</value>
|
||||
<value>This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password.</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutPolicyInEffect" xml:space="preserve">
|
||||
<value>Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is {0} hour(s) and {1} minute(s)</value>
|
||||
<value>Your organization policies have set your maximum allowed vault timeout to {0} hour(s) and {1} minute(s).</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutPolicyWithActionInEffect" xml:space="preserve">
|
||||
<value>Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is {0} hour(s) and {1} minute(s). Your vault timeout action is set to {2}.</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutActionPolicyInEffect" xml:space="preserve">
|
||||
<value>Your organization policies have set your vault timeout action to {0}.</value>
|
||||
</data>
|
||||
<data name="VaultTimeoutToLarge" xml:space="preserve">
|
||||
<value>Your vault timeout exceeds the restrictions set by your organization.</value>
|
||||
@@ -2604,4 +2610,10 @@ Do you want to switch to this account?</value>
|
||||
<data name="ThereAreNoItemsThatMatchTheSearch" xml:space="preserve">
|
||||
<value>There are no items that match the search</value>
|
||||
</data>
|
||||
<data name="UpdateWeakMasterPasswordWarning" xml:space="preserve">
|
||||
<value>Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour.</value>
|
||||
</data>
|
||||
<data name="CurrentMasterPassword" xml:space="preserve">
|
||||
<value>Current master password</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -2523,7 +2523,7 @@
|
||||
<value>此請求已失效</value>
|
||||
</data>
|
||||
<data name="PendingLogInRequests" xml:space="preserve">
|
||||
<value>Pending login requests</value>
|
||||
<value>待處理的登入請求。</value>
|
||||
</data>
|
||||
<data name="DeclineAllRequests" xml:space="preserve">
|
||||
<value>Decline all requests</value>
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<ProfileResponse> GetProfileAsync();
|
||||
Task<SyncResponse> GetSyncAsync();
|
||||
Task PostAccountKeysAsync(KeysRequest request);
|
||||
Task PostAccountVerifyPasswordAsync(PasswordVerificationRequest request);
|
||||
Task<VerifyMasterPasswordResponse> PostAccountVerifyPasswordAsync(PasswordVerificationRequest request);
|
||||
Task PostAccountRequestOTP();
|
||||
Task PostAccountVerifyOTPAsync(VerifyOTPRequest request);
|
||||
Task<CipherResponse> PostCipherAsync(CipherRequest request);
|
||||
@@ -62,6 +62,7 @@ namespace Bit.Core.Abstractions
|
||||
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
||||
Task PostEventsCollectAsync(IEnumerable<EventRequest> request);
|
||||
Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request);
|
||||
Task PostPasswordAsync(PasswordRequest request);
|
||||
Task DeleteAccountAsync(DeleteAccountRequest request);
|
||||
Task<OrganizationKeysResponse> GetOrganizationKeysAsync(string id);
|
||||
Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier);
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Bit.Core.Abstractions
|
||||
string orgId);
|
||||
Task<bool> PolicyAppliesToUser(PolicyType policyType, Func<Policy, bool> policyFilter = null, string userId = null);
|
||||
int? GetPolicyInt(Policy policy, string key);
|
||||
string GetPolicyString(Policy policy, string key);
|
||||
Task<bool> ShouldShowVaultFilterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,8 @@ namespace Bit.Core.Abstractions
|
||||
Task SetPushRegisteredTokenAsync(string value);
|
||||
Task<bool> GetUsesKeyConnectorAsync(string userId = null);
|
||||
Task SetUsesKeyConnectorAsync(bool? value, string userId = null);
|
||||
Task<ForcePasswordResetReason?> GetForcePasswordResetReasonAsync(string userId = null);
|
||||
Task SetForcePasswordResetReasonAsync(ForcePasswordResetReason? value, string userId = null);
|
||||
Task<Dictionary<string, OrganizationData>> GetOrganizationsAsync(string userId = null);
|
||||
Task SetOrganizationsAsync(Dictionary<string, OrganizationData> organizations, string userId = null);
|
||||
Task<PasswordGenerationOptions> GetPasswordGenerationOptionsAsync(string userId = null);
|
||||
|
||||
@@ -22,5 +22,6 @@ namespace Bit.Core.Abstractions
|
||||
Task LogOutAsync(bool userInitiated = true, string userId = null);
|
||||
Task SetVaultTimeoutOptionsAsync(int? timeout, VaultTimeoutAction? action);
|
||||
Task<int?> GetVaultTimeout(string userId = null);
|
||||
Task<VaultTimeoutAction?> GetVaultTimeoutAction(string userId = null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
public const string OtpAuthScheme = "otpauth";
|
||||
public const string AppLocaleKey = "appLocale";
|
||||
public const string ClearSensitiveFields = "clearSensitiveFields";
|
||||
public const string ForceUpdatePassword = "forceUpdatePassword";
|
||||
public const int SelectFileRequestCode = 42;
|
||||
public const int SelectFilePermissionRequestCode = 43;
|
||||
public const int SaveFileRequestCode = 44;
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace Bit.Core.Models.Domain
|
||||
EmailVerified = copy.EmailVerified;
|
||||
HasPremiumPersonally = copy.HasPremiumPersonally;
|
||||
AvatarColor = copy.AvatarColor;
|
||||
ForcePasswordResetReason = copy.ForcePasswordResetReason;
|
||||
}
|
||||
|
||||
public string UserId;
|
||||
@@ -66,6 +67,7 @@ namespace Bit.Core.Models.Domain
|
||||
public int? KdfParallelism;
|
||||
public bool? EmailVerified;
|
||||
public bool? HasPremiumPersonally;
|
||||
public ForcePasswordResetReason? ForcePasswordResetReason;
|
||||
}
|
||||
|
||||
public class AccountTokens
|
||||
|
||||
16
src/Core/Models/Domain/ForcePasswordResetReason.cs
Normal file
16
src/Core/Models/Domain/ForcePasswordResetReason.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Bit.Core.Models.Domain
|
||||
{
|
||||
public enum ForcePasswordResetReason
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when an organization admin forces a user to reset their password.
|
||||
/// </summary>
|
||||
AdminForcePasswordReset,
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a user logs in with a master password that does not meet an organization's master password
|
||||
/// policy that is enforced on login.
|
||||
/// </summary>
|
||||
WeakMasterPasswordOnLogin
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
public bool RequireLower { get; set; }
|
||||
public bool RequireNumbers { get; set; }
|
||||
public bool RequireSpecial { get; set; }
|
||||
public bool EnforceOnLogin { get; set; }
|
||||
|
||||
public bool InEffect()
|
||||
{
|
||||
|
||||
10
src/Core/Models/Request/PasswordRequest.cs
Normal file
10
src/Core/Models/Request/PasswordRequest.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Bit.Core.Models.Request
|
||||
{
|
||||
public class PasswordRequest
|
||||
{
|
||||
public string MasterPasswordHash { get; set; }
|
||||
public string NewMasterPasswordHash { get; set; }
|
||||
public string MasterPasswordHint { get; set; }
|
||||
public string Key { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using Bit.Core.Enums;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Models.Response
|
||||
@@ -24,6 +26,7 @@ namespace Bit.Core.Models.Response
|
||||
public int? KdfParallelism { get; set; }
|
||||
public bool ForcePasswordReset { get; set; }
|
||||
public string KeyConnectorUrl { get; set; }
|
||||
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
||||
[JsonIgnore]
|
||||
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Models.Response
|
||||
@@ -8,6 +9,7 @@ namespace Bit.Core.Models.Response
|
||||
{
|
||||
public List<TwoFactorProviderType> TwoFactorProviders { get; set; }
|
||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders2 { get; set; }
|
||||
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
||||
[JsonProperty("CaptchaBypassToken")]
|
||||
public string CaptchaToken { get; set; }
|
||||
}
|
||||
|
||||
9
src/Core/Models/Response/VerifyMasterPasswordResponse.cs
Normal file
9
src/Core/Models/Response/VerifyMasterPasswordResponse.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Models.Response
|
||||
{
|
||||
public class VerifyMasterPasswordResponse
|
||||
{
|
||||
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -176,10 +176,10 @@ namespace Bit.Core.Services
|
||||
return SendAsync<KeysRequest, object>(HttpMethod.Post, "/accounts/keys", request, true, false);
|
||||
}
|
||||
|
||||
public Task PostAccountVerifyPasswordAsync(PasswordVerificationRequest request)
|
||||
public Task<VerifyMasterPasswordResponse> PostAccountVerifyPasswordAsync(PasswordVerificationRequest request)
|
||||
{
|
||||
return SendAsync<PasswordVerificationRequest, object>(HttpMethod.Post, "/accounts/verify-password", request,
|
||||
true, false);
|
||||
return SendAsync<PasswordVerificationRequest, VerifyMasterPasswordResponse>(HttpMethod.Post, "/accounts/verify-password", request,
|
||||
true, true);
|
||||
}
|
||||
|
||||
public Task PostAccountRequestOTP()
|
||||
@@ -199,6 +199,11 @@ namespace Bit.Core.Services
|
||||
request, true, false);
|
||||
}
|
||||
|
||||
public Task PostPasswordAsync(PasswordRequest request)
|
||||
{
|
||||
return SendAsync<PasswordRequest, object>(HttpMethod.Post, "/accounts/password", request, true, false);
|
||||
}
|
||||
|
||||
public Task DeleteAccountAsync(DeleteAccountRequest request)
|
||||
{
|
||||
return SendAsync<DeleteAccountRequest, object>(HttpMethod.Delete, "/accounts", request, true, false);
|
||||
@@ -767,6 +772,12 @@ namespace Bit.Core.Services
|
||||
break;
|
||||
case ForwardedEmailServiceType.FirefoxRelay:
|
||||
requestMessage.Headers.Add("Authorization", $"Token {config.ApiToken}");
|
||||
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(
|
||||
new
|
||||
{
|
||||
enabled = true,
|
||||
description = "Generated by Bitwarden."
|
||||
}), Encoding.UTF8, "application/json");
|
||||
break;
|
||||
case ForwardedEmailServiceType.SimpleLogin:
|
||||
requestMessage.Headers.Add("Authentication", config.ApiToken);
|
||||
|
||||
@@ -26,11 +26,16 @@ namespace Bit.Core.Services
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly bool _setCryptoKeys;
|
||||
|
||||
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
|
||||
private SymmetricCryptoKey _key;
|
||||
|
||||
private string _authedUserId;
|
||||
private MasterPasswordPolicyOptions _masterPasswordPolicy;
|
||||
private ForcePasswordResetReason? _2faForcePasswordResetReason;
|
||||
|
||||
public AuthService(
|
||||
ICryptoService cryptoService,
|
||||
ICryptoFunctionService cryptoFunctionService,
|
||||
@@ -44,6 +49,7 @@ namespace Bit.Core.Services
|
||||
IVaultTimeoutService vaultTimeoutService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
IPasswordGenerationService passwordGenerationService,
|
||||
IPolicyService policyService,
|
||||
bool setCryptoKeys = true)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
@@ -57,6 +63,7 @@ namespace Bit.Core.Services
|
||||
_messagingService = messagingService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_passwordGenerationService = passwordGenerationService;
|
||||
_policyService = policyService;
|
||||
_setCryptoKeys = setCryptoKeys;
|
||||
|
||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||
@@ -136,11 +143,56 @@ namespace Bit.Core.Services
|
||||
public async Task<AuthResult> LogInAsync(string email, string masterPassword, string captchaToken)
|
||||
{
|
||||
SelectedTwoFactorProviderType = null;
|
||||
_2faForcePasswordResetReason = null;
|
||||
var key = await MakePreloginKeyAsync(masterPassword, email);
|
||||
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
||||
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
|
||||
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null,
|
||||
null, captchaToken);
|
||||
var result = await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null, captchaToken);
|
||||
|
||||
if (await RequirePasswordChangeAsync(email, masterPassword))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_authedUserId))
|
||||
{
|
||||
// Authentication was successful, save the WeakMasterPasswordOnLogin flag for the user
|
||||
result.ForcePasswordReset = true;
|
||||
await _stateService.SetForcePasswordResetReasonAsync(
|
||||
ForcePasswordResetReason.WeakMasterPasswordOnLogin, _authedUserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Authentication not fully successful (likely 2FA), store flag for LogInTwoFactorAsync()
|
||||
_2faForcePasswordResetReason = ForcePasswordResetReason.WeakMasterPasswordOnLogin;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the supplied master password against the master password policy provided by the Identity response.
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="masterPassword"></param>
|
||||
/// <returns>True if the master password does NOT meet any policy requirements, false otherwise (or if no policy present)</returns>
|
||||
private async Task<bool> RequirePasswordChangeAsync(string email, string masterPassword)
|
||||
{
|
||||
// No policy with EnforceOnLogin enabled, we're done.
|
||||
if (!(_masterPasswordPolicy is { EnforceOnLogin: true }))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var strength = _passwordGenerationService.PasswordStrength(
|
||||
masterPassword,
|
||||
_passwordGenerationService.GetPasswordStrengthUserInput(email)
|
||||
)?.Score;
|
||||
|
||||
if (!strength.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered)
|
||||
@@ -157,15 +209,26 @@ namespace Bit.Core.Services
|
||||
return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
|
||||
}
|
||||
|
||||
public Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
|
||||
public async Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
|
||||
string captchaToken, bool? remember = null)
|
||||
{
|
||||
if (captchaToken != null)
|
||||
{
|
||||
CaptchaToken = captchaToken;
|
||||
}
|
||||
return LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key,
|
||||
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key,
|
||||
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId);
|
||||
|
||||
// If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync()
|
||||
if (!string.IsNullOrEmpty(_authedUserId) && _2faForcePasswordResetReason.HasValue)
|
||||
{
|
||||
// Save the forcePasswordReset reason with the state service to force a password reset for the user
|
||||
result.ForcePasswordReset = true;
|
||||
await _stateService.SetForcePasswordResetReasonAsync(
|
||||
_2faForcePasswordResetReason, _authedUserId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<AuthResult> LogInCompleteAsync(string email, string masterPassword,
|
||||
@@ -367,6 +430,7 @@ namespace Bit.Core.Services
|
||||
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
|
||||
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
|
||||
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
|
||||
_masterPasswordPolicy = response.TwoFactorResponse.MasterPasswordPolicy;
|
||||
await _tokenService.ClearTwoFactorTokenAsync(email);
|
||||
return result;
|
||||
}
|
||||
@@ -374,6 +438,7 @@ namespace Bit.Core.Services
|
||||
var tokenResponse = response.TokenResponse;
|
||||
result.ResetMasterPassword = tokenResponse.ResetMasterPassword;
|
||||
result.ForcePasswordReset = tokenResponse.ForcePasswordReset;
|
||||
_masterPasswordPolicy = tokenResponse.MasterPasswordPolicy;
|
||||
if (tokenResponse.TwoFactorToken != null)
|
||||
{
|
||||
await _tokenService.SetTwoFactorTokenAsync(tokenResponse.TwoFactorToken, email);
|
||||
@@ -391,6 +456,9 @@ namespace Bit.Core.Services
|
||||
KdfMemory = tokenResponse.KdfMemory,
|
||||
KdfParallelism = tokenResponse.KdfParallelism,
|
||||
HasPremiumPersonally = _tokenService.GetPremium(),
|
||||
ForcePasswordResetReason = result.ForcePasswordReset
|
||||
? ForcePasswordResetReason.AdminForcePasswordReset
|
||||
: (ForcePasswordResetReason?)null,
|
||||
},
|
||||
new Account.AccountTokens()
|
||||
{
|
||||
@@ -473,6 +541,7 @@ namespace Bit.Core.Services
|
||||
|
||||
}
|
||||
|
||||
_authedUserId = _tokenService.GetUserId();
|
||||
await _stateService.SetBiometricLockedAsync(false);
|
||||
_messagingService.Send("loggedIn");
|
||||
return result;
|
||||
@@ -490,6 +559,8 @@ namespace Bit.Core.Services
|
||||
SsoRedirectUrl = null;
|
||||
TwoFactorProvidersData = null;
|
||||
SelectedTwoFactorProviderType = null;
|
||||
_masterPasswordPolicy = null;
|
||||
_authedUserId = null;
|
||||
}
|
||||
|
||||
public async Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync()
|
||||
|
||||
@@ -17,6 +17,11 @@ namespace Bit.Core.Services
|
||||
|
||||
private IEnumerable<Policy> _policyCache;
|
||||
|
||||
public const string TIMEOUT_POLICY_MINUTES = "minutes";
|
||||
public const string TIMEOUT_POLICY_ACTION = "action";
|
||||
public const string TIMEOUT_POLICY_ACTION_LOCK = "lock";
|
||||
public const string TIMEOUT_POLICY_ACTION_LOGOUT = "logOut";
|
||||
|
||||
public PolicyService(
|
||||
IStateService stateService,
|
||||
IOrganizationService organizationService)
|
||||
@@ -56,6 +61,12 @@ namespace Bit.Core.Services
|
||||
{
|
||||
await _stateService.SetEncryptedPoliciesAsync(policies, userId);
|
||||
_policyCache = null;
|
||||
|
||||
var vaultTimeoutPolicy = policies.FirstOrDefault(p => p.Value.Type == PolicyType.MaximumVaultTimeout);
|
||||
if (vaultTimeoutPolicy.Value != null)
|
||||
{
|
||||
await UpdateVaultTimeoutFromPolicyAsync(new Policy(vaultTimeoutPolicy.Value));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearAsync(string userId)
|
||||
@@ -64,6 +75,35 @@ namespace Bit.Core.Services
|
||||
_policyCache = null;
|
||||
}
|
||||
|
||||
public async Task UpdateVaultTimeoutFromPolicyAsync(Policy policy, string userId = null)
|
||||
{
|
||||
var policyTimeout = GetPolicyInt(policy, PolicyService.TIMEOUT_POLICY_MINUTES);
|
||||
if (policyTimeout != null)
|
||||
{
|
||||
var vaultTimeout = await _stateService.GetVaultTimeoutAsync(userId);
|
||||
var timeout = vaultTimeout.HasValue ? Math.Min(vaultTimeout.Value, policyTimeout.Value) : policyTimeout.Value;
|
||||
if (timeout < 0)
|
||||
{
|
||||
timeout = policyTimeout.Value;
|
||||
}
|
||||
if (vaultTimeout != timeout)
|
||||
{
|
||||
await _stateService.SetVaultTimeoutAsync(timeout, userId);
|
||||
}
|
||||
}
|
||||
|
||||
var policyAction = GetPolicyString(policy, PolicyService.TIMEOUT_POLICY_ACTION);
|
||||
if (!string.IsNullOrEmpty(policyAction))
|
||||
{
|
||||
var vaultTimeoutAction = await _stateService.GetVaultTimeoutActionAsync(userId);
|
||||
var action = policyAction == PolicyService.TIMEOUT_POLICY_ACTION_LOCK ? VaultTimeoutAction.Lock : VaultTimeoutAction.Logout;
|
||||
if (vaultTimeoutAction != action)
|
||||
{
|
||||
await _stateService.SetVaultTimeoutActionAsync(action, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MasterPasswordPolicyOptions> GetMasterPasswordPolicyOptions(
|
||||
IEnumerable<Policy> policies = null, string userId = null)
|
||||
{
|
||||
@@ -108,28 +148,34 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
var requireUpper = GetPolicyBool(currentPolicy, "requireUpper");
|
||||
if (requireUpper != null && (bool)requireUpper)
|
||||
if (requireUpper == true)
|
||||
{
|
||||
enforcedOptions.RequireUpper = true;
|
||||
}
|
||||
|
||||
var requireLower = GetPolicyBool(currentPolicy, "requireLower");
|
||||
if (requireLower != null && (bool)requireLower)
|
||||
if (requireLower == true)
|
||||
{
|
||||
enforcedOptions.RequireLower = true;
|
||||
}
|
||||
|
||||
var requireNumbers = GetPolicyBool(currentPolicy, "requireNumbers");
|
||||
if (requireNumbers != null && (bool)requireNumbers)
|
||||
if (requireNumbers == true)
|
||||
{
|
||||
enforcedOptions.RequireNumbers = true;
|
||||
}
|
||||
|
||||
var requireSpecial = GetPolicyBool(currentPolicy, "requireSpecial");
|
||||
if (requireSpecial != null && (bool)requireSpecial)
|
||||
if (requireSpecial == true)
|
||||
{
|
||||
enforcedOptions.RequireSpecial = true;
|
||||
}
|
||||
|
||||
var enforceOnLogin = GetPolicyBool(currentPolicy, "enforceOnLogin");
|
||||
if (enforceOnLogin == true)
|
||||
{
|
||||
enforcedOptions.EnforceOnLogin = true;
|
||||
}
|
||||
}
|
||||
|
||||
return enforcedOptions;
|
||||
@@ -247,6 +293,10 @@ namespace Bit.Core.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetPolicyString(Policy policy, string key) =>
|
||||
policy.Data.TryGetValue(key, out var val) ? val as string : null;
|
||||
|
||||
|
||||
public async Task<bool> ShouldShowVaultFilterAsync()
|
||||
{
|
||||
var personalOwnershipPolicyApplies = await PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
||||
@@ -272,17 +322,6 @@ namespace Bit.Core.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetPolicyString(Policy policy, string key)
|
||||
{
|
||||
if (policy.Data.ContainsKey(key))
|
||||
{
|
||||
var value = policy.Data[key];
|
||||
if (value != null)
|
||||
{
|
||||
return (string)value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1039,6 +1039,22 @@ namespace Bit.Core.Services
|
||||
await SetValueAsync(Constants.UsesKeyConnectorKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
public async Task<ForcePasswordResetReason?> GetForcePasswordResetReasonAsync(string userId = null)
|
||||
{
|
||||
var reconcileOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
return (await GetAccountAsync(reconcileOptions))?.Profile?.ForcePasswordResetReason;
|
||||
}
|
||||
|
||||
public async Task SetForcePasswordResetReasonAsync(ForcePasswordResetReason? value, string userId = null)
|
||||
{
|
||||
var reconcileOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultStorageOptionsAsync());
|
||||
var account = await GetAccountAsync(reconcileOptions);
|
||||
account.Profile.ForcePasswordResetReason = value;
|
||||
await SaveAccountAsync(account, reconcileOptions);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, OrganizationData>> GetOrganizationsAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
@@ -1412,6 +1428,7 @@ namespace Bit.Core.Services
|
||||
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
||||
await SetEncryptedSendsAsync(null, userId);
|
||||
await SetSettingsAsync(null, userId);
|
||||
await SetApprovePasswordlessLoginsAsync(null, userId);
|
||||
}
|
||||
|
||||
private async Task ScaffoldNewAccountAsync(Account account)
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Bit.Core.Services
|
||||
await SyncCollectionsAsync(response.Collections);
|
||||
await SyncCiphersAsync(userId, response.Ciphers);
|
||||
await SyncSettingsAsync(userId, response.Domains);
|
||||
await SyncPoliciesAsync(response.Policies);
|
||||
await SyncPoliciesAsync(userId, response.Policies);
|
||||
await SyncSendsAsync(userId, response.Sends);
|
||||
await SetLastSyncAsync(now);
|
||||
_watchDeviceService.Value.SyncDataToWatchAsync().FireAndForget();
|
||||
@@ -378,11 +378,11 @@ namespace Bit.Core.Services
|
||||
await _settingsService.SetEquivalentDomainsAsync(eqDomains);
|
||||
}
|
||||
|
||||
private async Task SyncPoliciesAsync(List<PolicyResponse> response)
|
||||
private async Task SyncPoliciesAsync(string userId, List<PolicyResponse> response)
|
||||
{
|
||||
var policies = response?.ToDictionary(p => p.Id, p => new PolicyData(p)) ??
|
||||
new Dictionary<string, PolicyData>();
|
||||
await _policyService.Replace(policies);
|
||||
await _policyService.Replace(policies, userId);
|
||||
}
|
||||
|
||||
private async Task SyncSendsAsync(string userId, List<SendResponse> response)
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Bit.Core.Services
|
||||
private readonly ISearchService _searchService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IKeyConnectorService _keyConnectorService;
|
||||
private readonly Func<Tuple<string, bool>, Task> _lockedCallback;
|
||||
private readonly Func<Tuple<string, bool, bool>, Task> _loggedOutCallback;
|
||||
@@ -32,7 +31,6 @@ namespace Bit.Core.Services
|
||||
ISearchService searchService,
|
||||
IMessagingService messagingService,
|
||||
ITokenService tokenService,
|
||||
IPolicyService policyService,
|
||||
IKeyConnectorService keyConnectorService,
|
||||
Func<Tuple<string, bool>, Task> lockedCallback,
|
||||
Func<Tuple<string, bool, bool>, Task> loggedOutCallback)
|
||||
@@ -46,7 +44,6 @@ namespace Bit.Core.Services
|
||||
_searchService = searchService;
|
||||
_messagingService = messagingService;
|
||||
_tokenService = tokenService;
|
||||
_policyService = policyService;
|
||||
_keyConnectorService = keyConnectorService;
|
||||
_lockedCallback = lockedCallback;
|
||||
_loggedOutCallback = loggedOutCallback;
|
||||
@@ -241,35 +238,12 @@ namespace Bit.Core.Services
|
||||
|
||||
public async Task<int?> GetVaultTimeout(string userId = null)
|
||||
{
|
||||
var vaultTimeout = await _stateService.GetVaultTimeoutAsync(userId);
|
||||
return await _stateService.GetVaultTimeoutAsync(userId);
|
||||
}
|
||||
|
||||
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId))
|
||||
{
|
||||
var policy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout, userId)).First();
|
||||
// Remove negative values, and ensure it's smaller than maximum allowed value according to policy
|
||||
var policyTimeout = _policyService.GetPolicyInt(policy, "minutes");
|
||||
if (!policyTimeout.HasValue)
|
||||
{
|
||||
return vaultTimeout;
|
||||
}
|
||||
|
||||
var timeout = vaultTimeout.HasValue ? Math.Min(vaultTimeout.Value, policyTimeout.Value) : policyTimeout.Value;
|
||||
|
||||
if (timeout < 0)
|
||||
{
|
||||
timeout = policyTimeout.Value;
|
||||
}
|
||||
|
||||
// We really shouldn't need to set the value here, but multiple services relies on this value being correct.
|
||||
if (vaultTimeout != timeout)
|
||||
{
|
||||
await _stateService.SetVaultTimeoutAsync(timeout, userId);
|
||||
}
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
return vaultTimeout;
|
||||
public async Task<VaultTimeoutAction?> GetVaultTimeoutAction(string userId = null)
|
||||
{
|
||||
return await _stateService.GetVaultTimeoutActionAsync(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Bit.Core.Utilities
|
||||
organizationService);
|
||||
var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService,
|
||||
folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
||||
policyService, keyConnectorService,
|
||||
keyConnectorService,
|
||||
(extras) =>
|
||||
{
|
||||
messagingService.Send("locked", extras);
|
||||
@@ -80,7 +80,7 @@ namespace Bit.Core.Utilities
|
||||
var totpService = new TotpService(cryptoFunctionService);
|
||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||
keyConnectorService, passwordGenerationService);
|
||||
keyConnectorService, passwordGenerationService, policyService);
|
||||
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
|
||||
|
||||
@@ -11,21 +11,6 @@ namespace Bit.iOS.Autofill
|
||||
get; set;
|
||||
}
|
||||
|
||||
public override void FinishedLaunching(UIApplication application)
|
||||
{
|
||||
|
||||
var ln = @"libbitwarden_c.framework/libbitwarden_c";
|
||||
var documentsPath = NSBundle.MainBundle.BundlePath;
|
||||
var filePath = System.IO.Path.Combine(documentsPath, "Frameworks", ln);
|
||||
var ptr = ObjCRuntime.Dlfcn.dlopen(filePath, 0);
|
||||
|
||||
var sdkClient = new BitwardenClient();
|
||||
var test = sdkClient.Fingerprint();
|
||||
|
||||
|
||||
base.FinishedLaunching(application);
|
||||
}
|
||||
|
||||
public override void OnResignActivation(UIApplication application)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.iOS
|
||||
{
|
||||
public class BitwardenClient : IDisposable
|
||||
{
|
||||
private readonly BitwardenClientSafeHandle handle;
|
||||
|
||||
public BitwardenClient()
|
||||
{
|
||||
handle = BitwardenClientWrapper.init("");
|
||||
}
|
||||
|
||||
public string Fingerprint()
|
||||
{
|
||||
return BitwardenClientWrapper.run_command("{}", handle.Ptr);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (handle != null && !handle.IsInvalid)
|
||||
handle.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Bit.iOS
|
||||
{
|
||||
internal class BitwardenClientSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public BitwardenClientSafeHandle() : base(true) { }
|
||||
|
||||
public IntPtr Ptr => this.handle;
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
BitwardenClientWrapper.free_mem(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Bit.iOS
|
||||
{
|
||||
internal static class BitwardenClientWrapper
|
||||
{
|
||||
#if Android
|
||||
const string DllName = "libBitwardenC.so";
|
||||
#else
|
||||
const string DllName = "__Internal";
|
||||
#endif
|
||||
|
||||
[DllImport(DllName, EntryPoint = "init")]
|
||||
internal static extern BitwardenClientSafeHandle init(string settings);
|
||||
|
||||
[DllImport(DllName, EntryPoint = "free_mem")]
|
||||
internal static extern void free_mem(IntPtr clientPtr);
|
||||
|
||||
[DllImport(DllName, EntryPoint = "run_command")]
|
||||
internal static extern string run_command(string loginRequest, IntPtr clientPtr);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.8bit.bitwarden.autofill</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2023.3.3</string>
|
||||
<string>2023.4.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
|
||||
@@ -194,9 +194,6 @@
|
||||
<InterfaceDefinition Include="MainInterface.storyboard" />
|
||||
<BundleResource Include="Resources\MaterialIcons_Regular.ttf" />
|
||||
<BundleResource Include="Resources\bwi-font.ttf" />
|
||||
<Compile Include="BitwardenClient.cs" />
|
||||
<Compile Include="BitwardenClientSafeHandle.cs" />
|
||||
<Compile Include="BitwardenClientWrapper.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
@@ -281,11 +278,5 @@
|
||||
<ItemGroup>
|
||||
<BundleResource Include="Resources\yubikey%403x.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<NativeReference Include="..\..\..\sdk\libbitwarden_c.framework">
|
||||
<Kind>Framework</Kind>
|
||||
<SmartLink>False</SmartLink>
|
||||
</NativeReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.AppExtension.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using MobileCoreServices;
|
||||
using Photos;
|
||||
@@ -29,11 +28,20 @@ namespace Bit.iOS.Core.Services
|
||||
var filePath = Path.Combine(GetTempPath(), fileName);
|
||||
File.WriteAllBytes(filePath, fileData);
|
||||
var url = NSUrl.FromFilename(filePath);
|
||||
var viewer = UIDocumentInteractionController.FromUrl(url);
|
||||
var controller = UIViewControllerExtensions.GetVisibleViewController();
|
||||
var rect = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad ?
|
||||
new CGRect(100, 5, 320, 320) : controller.View.Frame;
|
||||
return viewer.PresentOpenInMenu(rect, controller.View, true);
|
||||
|
||||
try
|
||||
{
|
||||
UIView presentingView = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
|
||||
var documentController = new UIDocumentPickerViewController(url, UIDocumentPickerMode.ExportToService);
|
||||
controller.PresentViewController(documentController, true, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanOpenFile(string fileName)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2023.3.3</string>
|
||||
<string>2023.4.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2023.3.3</string>
|
||||
<string>2023.4.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
|
||||
@@ -182,15 +182,7 @@ namespace Bit.iOS
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
}
|
||||
});
|
||||
|
||||
var ln = @"libbitwarden_c.framework/libbitwarden_c";
|
||||
var documentsPath = NSBundle.MainBundle.BundlePath;
|
||||
var filePath = System.IO.Path.Combine(documentsPath, "Frameworks", ln);
|
||||
var ptr = ObjCRuntime.Dlfcn.dlopen(filePath, 0);
|
||||
|
||||
var sdkClient = new BitwardenClient();
|
||||
var test = sdkClient.Fingerprint();
|
||||
|
||||
return base.FinishedLaunching(app, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.iOS
|
||||
{
|
||||
public class BitwardenClient : IDisposable
|
||||
{
|
||||
private readonly BitwardenClientSafeHandle handle;
|
||||
|
||||
public BitwardenClient()
|
||||
{
|
||||
handle = BitwardenClientWrapper.init("");
|
||||
}
|
||||
|
||||
public string Fingerprint()
|
||||
{
|
||||
return BitwardenClientWrapper.run_command("{}", handle.Ptr);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (handle != null && !handle.IsInvalid)
|
||||
handle.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Bit.iOS
|
||||
{
|
||||
internal class BitwardenClientSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public BitwardenClientSafeHandle() : base(true) { }
|
||||
|
||||
public IntPtr Ptr => this.handle;
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
BitwardenClientWrapper.free_mem(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Bit.iOS
|
||||
{
|
||||
internal static class BitwardenClientWrapper
|
||||
{
|
||||
#if Android
|
||||
const string DllName = "libBitwardenC.so";
|
||||
#else
|
||||
const string DllName = "__Internal";
|
||||
#endif
|
||||
|
||||
[DllImport(DllName, EntryPoint = "init")]
|
||||
internal static extern BitwardenClientSafeHandle init(string settings);
|
||||
|
||||
[DllImport(DllName, EntryPoint = "free_mem")]
|
||||
internal static extern void free_mem(IntPtr clientPtr);
|
||||
|
||||
[DllImport(DllName, EntryPoint = "run_command")]
|
||||
internal static extern string run_command(string loginRequest, IntPtr clientPtr);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.8bit.bitwarden</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2023.3.3</string>
|
||||
<string>2023.4.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleIconName</key>
|
||||
|
||||
@@ -183,9 +183,6 @@
|
||||
<ImageAsset Include="Resources\Assets.xcassets\empty_items_state.imageset\Empty-items-state-dark.pdf" />
|
||||
<ImageAsset Include="Resources\Assets.xcassets\empty_items_state.imageset\Empty-items-state.pdf" />
|
||||
<ImageAsset Include="Resources\Assets.xcassets\empty_items_state.imageset\Contents.json" />
|
||||
<Compile Include="BitwardenClient.cs" />
|
||||
<Compile Include="BitwardenClientSafeHandle.cs" />
|
||||
<Compile Include="BitwardenClientWrapper.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<InterfaceDefinition Include="LaunchScreen.storyboard" />
|
||||
@@ -427,12 +424,6 @@
|
||||
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<NativeReference Include="..\..\..\sdk\libbitwarden_c.framework">
|
||||
<Kind>Framework</Kind>
|
||||
<SmartLink>False</SmartLink>
|
||||
</NativeReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(_ResolvedWatchAppReferences)' != '' ">
|
||||
<CodesignExtraArgs>--deep</CodesignExtraArgs>
|
||||
</PropertyGroup>
|
||||
|
||||
Reference in New Issue
Block a user