1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-07 11:03:54 +00:00

[PM-1208] Add TDE flows for new users (#2655)

* [PM-1208] Create new user on SSO. Logout if not password is setup or has pending admin auth request.

* [PM-1208] Fix new user UserKey decryption.

* [PM-1208] Add new user continue to vault logic. Auto enrol user on continue.

* [PM-1208] Trust device only if needed

* [PM-1208] Add logic for New User SSO.

* [PM-1208] Add logic for New User SSO (missing file).
This commit is contained in:
André Bispo
2023-08-03 18:20:51 +01:00
committed by GitHub
parent 78788276ef
commit c75bcccf20
15 changed files with 271 additions and 70 deletions

View File

@@ -164,6 +164,13 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null)
{
await _vaultTimeoutService.LogOutAsync();
return;
}
_pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
@@ -173,6 +180,17 @@ namespace Bit.App.Pages
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasEncryptedUserKeyAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
if (await _stateService.IsAuthenticatedAsync()
&& decryptOptions?.TrustedDeviceOption != null
&& !decryptOptions.HasMasterPassword
&& !BiometricEnabled
&& !PinEnabled)
{
await _vaultTimeoutService.LogOutAsync();
return;
}
// Users with key connector and without biometric or pin has no MP to unlock with
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))

View File

@@ -34,7 +34,7 @@
Text="{u:I18n Continue}"
StyleClass="btn-primary"
Command="{Binding ContinueCommand}"
IsVisible="{Binding ContinueEnabled}"/>
IsVisible="{Binding IsNewUser}"/>
<Button
x:Name="_approveWithMyOtherDevice"
Text="{u:I18n ApproveWithMyOtherDevice}"

View File

@@ -19,9 +19,10 @@ namespace Bit.App.Pages
{
InitializeComponent();
_vm = BindingContext as LoginApproveDeviceViewModel;
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPassword().FireAndForget();
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPasswordAsync().FireAndForget();
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
_vm.ContinueToVaultAction = () => ContinueToVaultAsync().FireAndForget();
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
_vm.Page = this;
_appOptions = appOptions;
@@ -40,7 +41,17 @@ namespace Bit.App.Pages
}
}
private async Task StartLogInWithMasterPassword()
private async Task ContinueToVaultAsync()
{
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task StartLogInWithMasterPasswordAsync()
{
var page = new LockPage(_appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));

View File

@@ -1,4 +1,5 @@
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
@@ -6,6 +7,7 @@ using Bit.App.Resources;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities;
@@ -21,11 +23,12 @@ namespace Bit.App.Pages
private bool _approveWithMyOtherDeviceEnabled;
private bool _requestAdminApprovalEnabled;
private bool _approveWithMasterPasswordEnabled;
private bool _continueEnabled;
private string _email;
private readonly IStateService _stateService;
private readonly IApiService _apiService;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
public ICommand ApproveWithMyOtherDeviceCommand { get; }
public ICommand RequestAdminApprovalCommand { get; }
@@ -35,6 +38,7 @@ namespace Bit.App.Pages
public Action LogInWithMasterPasswordAction { get; set; }
public Action LogInWithDeviceAction { get; set; }
public Action RequestAdminApprovalAction { get; set; }
public Action ContinueToVaultAction { get; set; }
public Action CloseAction { get; set; }
public LoginApproveDeviceViewModel()
@@ -42,6 +46,8 @@ namespace Bit.App.Pages
_stateService = ServiceContainer.Resolve<IStateService>();
_apiService = ServiceContainer.Resolve<IApiService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_authService = ServiceContainer.Resolve<IAuthService>();
_syncService = ServiceContainer.Resolve<ISyncService>();
PageTitle = AppResources.LoggedIn;
@@ -57,7 +63,7 @@ namespace Bit.App.Pages
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
ContinueCommand = new AsyncCommand(InitAsync,
ContinueCommand = new AsyncCommand(CreateNewSsoUserAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
}
@@ -79,20 +85,18 @@ namespace Bit.App.Pages
public bool RequestAdminApprovalEnabled
{
get => _requestAdminApprovalEnabled;
set => SetProperty(ref _requestAdminApprovalEnabled, value);
set => SetProperty(ref _requestAdminApprovalEnabled, value,
additionalPropertyNames: new[] { nameof(IsNewUser) });
}
public bool ApproveWithMasterPasswordEnabled
{
get => _approveWithMasterPasswordEnabled;
set => SetProperty(ref _approveWithMasterPasswordEnabled, value);
set => SetProperty(ref _approveWithMasterPasswordEnabled, value,
additionalPropertyNames: new[] { nameof(IsNewUser) });
}
public bool ContinueEnabled
{
get => _continueEnabled;
set => SetProperty(ref _continueEnabled, value);
}
public bool IsNewUser => !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled;
public string Email
{
@@ -117,9 +121,18 @@ namespace Bit.App.Pages
{
HandleException(ex);
}
}
// TODO: Change this expression to, Appear if the browser is trusted and shared the key with the app
ContinueEnabled = !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled && !ApproveWithMyOtherDeviceEnabled;
public async Task CreateNewSsoUserAsync()
{
await _authService.CreateNewSsoUserAsync(await _stateService.GetRememberedOrgIdentifierAsync());
if (RememberThisDevice)
{
await _deviceTrustCryptoService.TrustDeviceAsync();
}
_syncService.FullSyncAsync(true).FireAndForget();
await Device.InvokeOnMainThreadAsync(ContinueToVaultAction);
}
private async Task SetDeviceTrustAndInvokeAsync(Action action)

View File

@@ -209,17 +209,12 @@ namespace Bit.App.Pages
if (response.TwoFactor)
{
StartTwoFactorAction?.Invoke();
return;
}
else if (response.ResetMasterPassword)
{
StartSetPasswordAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (decryptOptions?.TrustedDeviceOption != null)
if (decryptOptions?.TrustedDeviceOption != null)
{
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
@@ -235,18 +230,52 @@ namespace Bit.App.Pages
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
else if (pendingRequest != null)
{
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
if (authRequest != null && authRequest.RequestApproved != null && authRequest.RequestApproved.Value)
{
var authResult = await _authService.LogInPasswordlessAsync(await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
if (authResult == null && await _stateService.IsAuthenticatedAsync())
{
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
await _stateService.SetPendingAdminAuthRequestAsync(null);
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
return;
}
else
// In the standard, non TDE case, a user must set password if they don't
// have one and they aren't using key connector.
// Note: TDE & Key connector are mutually exclusive org config options.
if (response.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false))
{
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
StartSetPasswordAction?.Invoke();
return;
}
if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
catch (Exception e)
catch (Exception)
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,

View File

@@ -3784,6 +3784,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Login approved.
/// </summary>
public static string LoginApproved {
get {
return ResourceManager.GetString("LoginApproved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login attempt by {0} on {1}.
/// </summary>

View File

@@ -2750,4 +2750,7 @@ Do you want to switch to this account?</value>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Cannot edit multiple URIs at once</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login approved</value>
</data>
</root>