1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

..

10 Commits

Author SHA1 Message Date
github-actions[bot]
cc350cf20c Bumped version to 2023.4.0 (#2496)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
(cherry picked from commit e820537fce)
2023-04-26 13:49:02 +02:00
André Bispo
8f59e79fac [PM-1946] remove ApprovePasswordlessLogins value on logout (#2494)
(cherry picked from commit 7130d8a18c)
2023-04-25 09:53:22 -04:00
Jake Fink
0b51796b63 [PM-1906] check value of KeyValuePair for null instead of object (#2489) 2023-04-21 11:26:07 -04:00
Opeyemi
3bcb44ea71 changing CI-only SP KV job names (#2484) 2023-04-18 11:48:05 +01:00
Shane Melton
b108b4e71d [AC-1070] Enforce master password policy on login/unlock (#2410)
* [AC-1070] Add EnforceOnLogin property to MasterPasswordPolicyOptions

* [AC-1070] Add MasterPasswordPolicy property to Identity responses

* [AC-1070] Add policy service dependency to auth service

* [AC-1070] Introduce logic to evaluate master password after successful login

* [AC-1070] Add optional ForcePasswordResetReason to profile / state service

* [AC-1070] Save ForcePasswordResetReason to state when a weak master password is found during login

- Additionally, save the AdminForcePasswordReset reason if the identity result indicates an admin password reset is in effect.

* [AC-1070] Check for a saved ForcePasswordReset reason on TabsPage load force show the update password page

* [AC-1070] Make InitAsync virtual

Allow the UpdateTempPasswordPage to override the InitAsync method to check for a reset password reason in the state service

* [AC-1070] Modify UpdateTempPassword page appearance

- Load the force password reset reason from the state service
- Make warning text dynamic based on force password reason
- Conditionally show the Current master password field if updating a weak master password

* [AC-1070] Add update password method to Api service

* [AC-1070] Introduce logic to update both temp and regular passwords

- Check the Reason to use the appropriate request/endpoint when submitting.
- Verify the users current password locally using the user verification service.

* [AC-1070] Introduce VerifyMasterPasswordResponse

* [AC-1070] Add logic to evaluate master password on unlock

* [AC-1070] Add support 2FA login flow

Keep track of the reset password reason after a password login requires 2FA. During 2FA submission, check if there is a saved reason, and if so, force the user to update their password.

* [AC-1070] Formatting

* [AC-1070] Remove string key from service resolution

* [AC-1070] Change master password options to method variable to avoid class field

Add null check for password strength result and log an error as this is an unexpected flow

* [AC-1070] Remove usage of i18nService

* [AC-1070] Use AsyncCommand for SubmitCommand

* [AC-1070] Remove type from ShowToast call

* [AC-1070] Simplify UpdatePassword methods to accept string for the new encryption key

* [AC-1070] Use full text for key for the CurrentMasterPassword resource

* [AC-1070] Convert Reason to a private class field

* [AC-1070] Formatting changes

* [AC-1070] Simplify if statements in master password options policy service method

* [AC-1070] Use the saved force password reset reason after 2FA login

* [AC-1070] Use constant for ForceUpdatePassword message command

* [AC-1070] Move shared RequirePasswordChangeOnLogin method into PolicyService

* Revert "[AC-1070] Move shared RequirePasswordChangeOnLogin method into PolicyService"

This reverts commit e4feac130f.

* [AC-1070] Add check for null password strength response

* [AC-1070] Fix broken show password icon

* [AC-1070] Add show password icon for current master password
2023-04-17 07:35:50 -07:00
Jake Fink
a72f267558 [AC-1045] vault timeout action policy (#2415)
* [EC-1045] lock action if policy and show message

* [EC-1045] add text for policy message

* [EC-1045] add consts to policy service

* [EC-1045] missed a const

* [AC-1045] fix build

* [AC-1045] fix bug where UI wasn't updating after sync

* [AC-1045] change FirstOrDefault to First to avoid nulls

* [AC-1045] refactor get vault timeout functions

* [AC-1045] don't filter action options unecessarily

* [AC-1045] refactor build alert logic for readability

* [AC-1045] use policy to filter timeout options instead of current timeout

* [AC-1045] update timeout during sync instead of getter
- remove encrypted from state since it's not encrypted
- if policies return a timeout policy, check and update vault timeout

* [AC-1045] default to custom if we can't find vault timeout option

* [AC-1045] revert Encrypted Policies rename
2023-04-14 15:39:57 -04:00
github-actions[bot]
cc75cebdb8 Autosync the updated translations (#2476)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-04-14 20:35:29 +02:00
Daniel James Smith
3a0510d6b4 [PS-2507] Enable firefox relay address on creation (#2474)
* Enable firefox relay address on creation

Adding a body (json) to the request and setting enabled to true.
Additionally the description is set to "Generated by Bitwarden." to mimick the behaviour of the other clients

* Add missing encoding and mediaType

* Replace JObject with anonymous type
2023-04-14 19:20:35 +02:00
aj-rosado
0c4b88e562 PM-1731 - Changed UIDocumentInteractionController with UIDocumentPickerViewController (#2472) 2023-04-13 19:51:56 +01:00
Michał Chęciński
ac3b0c2bad [DEVOPS-1261] Update workflows to use new CI only keyvault (#2462)
* Fixed warning in version-bump

* Use new CI Azure Key Vault

* Fix name
2023-04-11 17:18:59 +02:00
52 changed files with 583 additions and 338 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}
}

View File

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

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
using Bit.Core.Models.Domain;
namespace Bit.Core.Models.Response
{
public class VerifyMasterPasswordResponse
{
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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