mirror of
https://github.com/bitwarden/mobile
synced 2025-12-12 22:33:25 +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:
@@ -164,6 +164,13 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
|
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
||||||
|
if (pendingRequest != null)
|
||||||
|
{
|
||||||
|
await _vaultTimeoutService.LogOutAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
|
_pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
|
|
||||||
var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
|
var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
|
||||||
@@ -173,6 +180,17 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasEncryptedUserKeyAsync();
|
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
|
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||||
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
|
if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
Text="{u:I18n Continue}"
|
Text="{u:I18n Continue}"
|
||||||
StyleClass="btn-primary"
|
StyleClass="btn-primary"
|
||||||
Command="{Binding ContinueCommand}"
|
Command="{Binding ContinueCommand}"
|
||||||
IsVisible="{Binding ContinueEnabled}"/>
|
IsVisible="{Binding IsNewUser}"/>
|
||||||
<Button
|
<Button
|
||||||
x:Name="_approveWithMyOtherDevice"
|
x:Name="_approveWithMyOtherDevice"
|
||||||
Text="{u:I18n ApproveWithMyOtherDevice}"
|
Text="{u:I18n ApproveWithMyOtherDevice}"
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_vm = BindingContext as LoginApproveDeviceViewModel;
|
_vm = BindingContext as LoginApproveDeviceViewModel;
|
||||||
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPassword().FireAndForget();
|
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPasswordAsync().FireAndForget();
|
||||||
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
|
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
|
||||||
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
|
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
|
||||||
|
_vm.ContinueToVaultAction = () => ContinueToVaultAsync().FireAndForget();
|
||||||
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
|
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_appOptions = appOptions;
|
_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);
|
var page = new LockPage(_appOptions);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
@@ -6,6 +7,7 @@ using Bit.App.Resources;
|
|||||||
using Bit.App.Utilities.AccountManagement;
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@@ -21,11 +23,12 @@ namespace Bit.App.Pages
|
|||||||
private bool _approveWithMyOtherDeviceEnabled;
|
private bool _approveWithMyOtherDeviceEnabled;
|
||||||
private bool _requestAdminApprovalEnabled;
|
private bool _requestAdminApprovalEnabled;
|
||||||
private bool _approveWithMasterPasswordEnabled;
|
private bool _approveWithMasterPasswordEnabled;
|
||||||
private bool _continueEnabled;
|
|
||||||
private string _email;
|
private string _email;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IApiService _apiService;
|
private readonly IApiService _apiService;
|
||||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
private readonly IAuthService _authService;
|
||||||
|
private readonly ISyncService _syncService;
|
||||||
|
|
||||||
public ICommand ApproveWithMyOtherDeviceCommand { get; }
|
public ICommand ApproveWithMyOtherDeviceCommand { get; }
|
||||||
public ICommand RequestAdminApprovalCommand { get; }
|
public ICommand RequestAdminApprovalCommand { get; }
|
||||||
@@ -35,6 +38,7 @@ namespace Bit.App.Pages
|
|||||||
public Action LogInWithMasterPasswordAction { get; set; }
|
public Action LogInWithMasterPasswordAction { get; set; }
|
||||||
public Action LogInWithDeviceAction { get; set; }
|
public Action LogInWithDeviceAction { get; set; }
|
||||||
public Action RequestAdminApprovalAction { get; set; }
|
public Action RequestAdminApprovalAction { get; set; }
|
||||||
|
public Action ContinueToVaultAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
public LoginApproveDeviceViewModel()
|
public LoginApproveDeviceViewModel()
|
||||||
@@ -42,6 +46,8 @@ namespace Bit.App.Pages
|
|||||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
_apiService = ServiceContainer.Resolve<IApiService>();
|
_apiService = ServiceContainer.Resolve<IApiService>();
|
||||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
_authService = ServiceContainer.Resolve<IAuthService>();
|
||||||
|
_syncService = ServiceContainer.Resolve<ISyncService>();
|
||||||
|
|
||||||
PageTitle = AppResources.LoggedIn;
|
PageTitle = AppResources.LoggedIn;
|
||||||
|
|
||||||
@@ -57,7 +63,7 @@ namespace Bit.App.Pages
|
|||||||
onException: ex => HandleException(ex),
|
onException: ex => HandleException(ex),
|
||||||
allowsMultipleExecutions: false);
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
ContinueCommand = new AsyncCommand(InitAsync,
|
ContinueCommand = new AsyncCommand(CreateNewSsoUserAsync,
|
||||||
onException: ex => HandleException(ex),
|
onException: ex => HandleException(ex),
|
||||||
allowsMultipleExecutions: false);
|
allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
@@ -79,20 +85,18 @@ namespace Bit.App.Pages
|
|||||||
public bool RequestAdminApprovalEnabled
|
public bool RequestAdminApprovalEnabled
|
||||||
{
|
{
|
||||||
get => _requestAdminApprovalEnabled;
|
get => _requestAdminApprovalEnabled;
|
||||||
set => SetProperty(ref _requestAdminApprovalEnabled, value);
|
set => SetProperty(ref _requestAdminApprovalEnabled, value,
|
||||||
|
additionalPropertyNames: new[] { nameof(IsNewUser) });
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ApproveWithMasterPasswordEnabled
|
public bool ApproveWithMasterPasswordEnabled
|
||||||
{
|
{
|
||||||
get => _approveWithMasterPasswordEnabled;
|
get => _approveWithMasterPasswordEnabled;
|
||||||
set => SetProperty(ref _approveWithMasterPasswordEnabled, value);
|
set => SetProperty(ref _approveWithMasterPasswordEnabled, value,
|
||||||
|
additionalPropertyNames: new[] { nameof(IsNewUser) });
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContinueEnabled
|
public bool IsNewUser => !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled;
|
||||||
{
|
|
||||||
get => _continueEnabled;
|
|
||||||
set => SetProperty(ref _continueEnabled, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Email
|
public string Email
|
||||||
{
|
{
|
||||||
@@ -117,9 +121,18 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
HandleException(ex);
|
HandleException(ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Change this expression to, Appear if the browser is trusted and shared the key with the app
|
public async Task CreateNewSsoUserAsync()
|
||||||
ContinueEnabled = !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled && !ApproveWithMyOtherDeviceEnabled;
|
{
|
||||||
|
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)
|
private async Task SetDeviceTrustAndInvokeAsync(Action action)
|
||||||
|
|||||||
@@ -209,17 +209,12 @@ namespace Bit.App.Pages
|
|||||||
if (response.TwoFactor)
|
if (response.TwoFactor)
|
||||||
{
|
{
|
||||||
StartTwoFactorAction?.Invoke();
|
StartTwoFactorAction?.Invoke();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (response.ResetMasterPassword)
|
|
||||||
{
|
if (decryptOptions?.TrustedDeviceOption != null)
|
||||||
StartSetPasswordAction?.Invoke();
|
|
||||||
}
|
|
||||||
else if (response.ForcePasswordReset)
|
|
||||||
{
|
|
||||||
UpdateTempPasswordAction?.Invoke();
|
|
||||||
}
|
|
||||||
else 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 user doesn't have a MP, but has reset password permission, they must set a MP
|
||||||
if (!decryptOptions.HasMasterPassword &&
|
if (!decryptOptions.HasMasterPassword &&
|
||||||
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
||||||
@@ -235,6 +230,21 @@ namespace Bit.App.Pages
|
|||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
SsoAuthSuccessAction?.Invoke();
|
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
|
else
|
||||||
{
|
{
|
||||||
StartDeviceApprovalOptionsAction?.Invoke();
|
StartDeviceApprovalOptionsAction?.Invoke();
|
||||||
@@ -242,11 +252,30 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
StartDeviceApprovalOptionsAction?.Invoke();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
{
|
||||||
|
StartSetPasswordAction?.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.ForcePasswordReset)
|
||||||
|
{
|
||||||
|
UpdateTempPasswordAction?.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
SsoAuthSuccessAction?.Invoke();
|
SsoAuthSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
catch (Exception)
|
||||||
catch (Exception e)
|
|
||||||
{
|
{
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
||||||
|
|||||||
9
src/App/Resources/AppResources.Designer.cs
generated
9
src/App/Resources/AppResources.Designer.cs
generated
@@ -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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Login attempt by {0} on {1}.
|
/// Looks up a localized string similar to Login attempt by {0} on {1}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2750,4 +2750,7 @@ Do you want to switch to this account?</value>
|
|||||||
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
|
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
|
||||||
<value>Cannot edit multiple URIs at once</value>
|
<value>Cannot edit multiple URIs at once</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LoginApproved" xml:space="preserve">
|
||||||
|
<value>Login approved</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode);
|
Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode);
|
||||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||||
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);
|
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);
|
||||||
|
Task CreateNewSsoUserAsync(string organizationSsoId);
|
||||||
|
|
||||||
void LogOut(Action callback);
|
void LogOut(Action callback);
|
||||||
void Init();
|
void Init();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
@@ -10,7 +11,8 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<bool> GetUsesKeyConnector();
|
Task<bool> GetUsesKeyConnector();
|
||||||
Task<bool> UserNeedsMigration();
|
Task<bool> UserNeedsMigration();
|
||||||
Task MigrateUser();
|
Task MigrateUser();
|
||||||
Task GetAndSetKey(string url);
|
Task GetAndSetKeyAsync(string url);
|
||||||
Task<Organization> GetManagingOrganization();
|
Task<Organization> GetManagingOrganization();
|
||||||
|
Task ConvertNewUserToKeyConnectorAsync(string orgId, IdentityTokenResponse tokenResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/Core/Abstractions/IPasswordResetEnrollmentService.cs
Normal file
12
src/Core/Abstractions/IPasswordResetEnrollmentService.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IPasswordResetEnrollmentService
|
||||||
|
{
|
||||||
|
Task EnrollIfRequiredAsync(string organizationSsoId);
|
||||||
|
Task EnrollAsync(string organizationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -6,6 +6,8 @@ namespace Bit.Core.Models.Domain
|
|||||||
public bool HasMasterPassword { get; set; }
|
public bool HasMasterPassword { get; set; }
|
||||||
public TrustedDeviceOption TrustedDeviceOption { get; set; }
|
public TrustedDeviceOption TrustedDeviceOption { get; set; }
|
||||||
public KeyConnectorOption KeyConnectorOption { get; set; }
|
public KeyConnectorOption KeyConnectorOption { get; set; }
|
||||||
|
|
||||||
|
public bool RequireSetPassword => !HasMasterPassword && KeyConnectorOption == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TrustedDeviceOption
|
public class TrustedDeviceOption
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ namespace Bit.Core.Services
|
|||||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
private readonly IPasswordResetEnrollmentService _passwordResetEnrollmentService;
|
||||||
private readonly bool _setCryptoKeys;
|
private readonly bool _setCryptoKeys;
|
||||||
|
|
||||||
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
|
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
|
||||||
@@ -52,6 +53,7 @@ namespace Bit.Core.Services
|
|||||||
IPasswordGenerationService passwordGenerationService,
|
IPasswordGenerationService passwordGenerationService,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
IDeviceTrustCryptoService deviceTrustCryptoService,
|
IDeviceTrustCryptoService deviceTrustCryptoService,
|
||||||
|
IPasswordResetEnrollmentService passwordResetEnrollmentService,
|
||||||
bool setCryptoKeys = true)
|
bool setCryptoKeys = true)
|
||||||
{
|
{
|
||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
@@ -67,6 +69,7 @@ namespace Bit.Core.Services
|
|||||||
_passwordGenerationService = passwordGenerationService;
|
_passwordGenerationService = passwordGenerationService;
|
||||||
_policyService = policyService;
|
_policyService = policyService;
|
||||||
_deviceTrustCryptoService = deviceTrustCryptoService;
|
_deviceTrustCryptoService = deviceTrustCryptoService;
|
||||||
|
_passwordResetEnrollmentService = passwordResetEnrollmentService;
|
||||||
_setCryptoKeys = setCryptoKeys;
|
_setCryptoKeys = setCryptoKeys;
|
||||||
|
|
||||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||||
@@ -505,12 +508,9 @@ namespace Bit.Core.Services
|
|||||||
await _cryptoService.SetUserKeyAsync(userKey);
|
await _cryptoService.SetUserKeyAsync(userKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code == null || tokenResponse.Key != null)
|
|
||||||
{
|
|
||||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
var hasUserKey = await _cryptoService.HasUserKeyAsync();
|
||||||
|
if (decryptOptions?.TrustedDeviceOption != null && !hasUserKey)
|
||||||
if (decryptOptions?.TrustedDeviceOption != null)
|
|
||||||
{
|
{
|
||||||
var key = await _deviceTrustCryptoService.DecryptUserKeyWithDeviceKeyAsync(decryptOptions.TrustedDeviceOption.EncryptedPrivateKey, decryptOptions.TrustedDeviceOption.EncryptedUserKey);
|
var key = await _deviceTrustCryptoService.DecryptUserKeyWithDeviceKeyAsync(decryptOptions.TrustedDeviceOption.EncryptedPrivateKey, decryptOptions.TrustedDeviceOption.EncryptedUserKey);
|
||||||
if (key != null)
|
if (key != null)
|
||||||
@@ -518,7 +518,11 @@ namespace Bit.Core.Services
|
|||||||
await _cryptoService.SetUserKeyAsync(key);
|
await _cryptoService.SetUserKeyAsync(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
|
|
||||||
|
if (code == null || tokenResponse.Key != null)
|
||||||
|
{
|
||||||
|
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
||||||
|
if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
|
||||||
{
|
{
|
||||||
|
|
||||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
||||||
@@ -558,37 +562,15 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
else if (tokenResponse.KeyConnectorUrl != null)
|
else if (tokenResponse.KeyConnectorUrl != null)
|
||||||
{
|
{
|
||||||
// SSO Key Connector Onboarding
|
// New User has tokenResponse.Key == null
|
||||||
var password = await _cryptoFunctionService.RandomBytesAsync(64);
|
if (tokenResponse.Key == null)
|
||||||
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
|
|
||||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
|
|
||||||
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
|
||||||
|
|
||||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
|
|
||||||
newMasterKey,
|
|
||||||
await _cryptoService.MakeUserKeyAsync());
|
|
||||||
|
|
||||||
await _cryptoService.SetUserKeyAsync(newUserKey);
|
|
||||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
await _apiService.PostUserKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
|
await _keyConnectorService.ConvertNewUserToKeyConnectorAsync(orgId, tokenResponse);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Unable to reach Key Connector", e);
|
await _keyConnectorService.GetAndSetKeyAsync(tokenResponse.KeyConnectorUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
var keys = new KeysRequest
|
|
||||||
{
|
|
||||||
PublicKey = newPublicKey,
|
|
||||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
|
||||||
};
|
|
||||||
var setPasswordRequest = new SetKeyConnectorKeyRequest(
|
|
||||||
newProtectedPrivateKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
|
|
||||||
);
|
|
||||||
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,5 +676,22 @@ namespace Bit.Core.Services
|
|||||||
passwordlessLogin.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(userEmail, CoreHelpers.Base64UrlDecode(passwordlessLogin.PublicKey)));
|
passwordlessLogin.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(userEmail, CoreHelpers.Base64UrlDecode(passwordlessLogin.PublicKey)));
|
||||||
return passwordlessLogin;
|
return passwordlessLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task CreateNewSsoUserAsync(string organizationSsoId)
|
||||||
|
{
|
||||||
|
var orgAutoEnrollStatusResponse = await _apiService.GetOrganizationAutoEnrollStatusAsync(organizationSsoId);
|
||||||
|
var randomBytes = _cryptoFunctionService.RandomBytes(64);
|
||||||
|
var userKey = new UserKey(randomBytes);
|
||||||
|
var (userPubKey, userPrivKey) = await _cryptoService.MakeKeyPairAsync(userKey);
|
||||||
|
await _apiService.PostAccountKeysAsync(new KeysRequest
|
||||||
|
{
|
||||||
|
PublicKey = userPubKey,
|
||||||
|
EncryptedPrivateKey = userPrivKey.EncryptedString
|
||||||
|
});
|
||||||
|
|
||||||
|
await _stateService.SetUserKeyAsync(userKey);
|
||||||
|
await _stateService.SetPrivateKeyEncryptedAsync(userPrivKey.EncryptedString);
|
||||||
|
await _passwordResetEnrollmentService.EnrollAsync(orgAutoEnrollStatusResponse.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@@ -12,19 +13,21 @@ namespace Bit.Core.Services
|
|||||||
private readonly ICryptoService _cryptoService;
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private readonly IApiService _apiService;
|
private readonly IApiService _apiService;
|
||||||
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
|
|
||||||
public KeyConnectorService(IStateService stateService, ICryptoService cryptoService,
|
public KeyConnectorService(IStateService stateService, ICryptoService cryptoService,
|
||||||
ITokenService tokenService, IApiService apiService, OrganizationService organizationService)
|
ITokenService tokenService, IApiService apiService, ICryptoFunctionService cryptoFunctionService, OrganizationService organizationService)
|
||||||
{
|
{
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
_tokenService = tokenService;
|
_tokenService = tokenService;
|
||||||
_apiService = apiService;
|
_apiService = apiService;
|
||||||
|
_cryptoFunctionService = cryptoFunctionService;
|
||||||
_organizationService = organizationService;
|
_organizationService = organizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GetAndSetKey(string url)
|
public async Task GetAndSetKeyAsync(string url)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -83,5 +86,40 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
|
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ConvertNewUserToKeyConnectorAsync(string orgId, IdentityTokenResponse tokenResponse)
|
||||||
|
{
|
||||||
|
// SSO Key Connector Onboarding
|
||||||
|
var password = await _cryptoFunctionService.RandomBytesAsync(64);
|
||||||
|
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
|
||||||
|
var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
|
||||||
|
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
||||||
|
|
||||||
|
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
|
||||||
|
newMasterKey,
|
||||||
|
await _cryptoService.MakeUserKeyAsync());
|
||||||
|
|
||||||
|
await _cryptoService.SetUserKeyAsync(newUserKey);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _apiService.PostUserKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("Unable to reach Key Connector", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync();
|
||||||
|
var keys = new KeysRequest
|
||||||
|
{
|
||||||
|
PublicKey = newPublicKey,
|
||||||
|
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
||||||
|
};
|
||||||
|
var setPasswordRequest = new SetKeyConnectorKeyRequest(
|
||||||
|
newProtectedPrivateKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
|
||||||
|
);
|
||||||
|
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/Core/Services/PasswordResetEnrollmentService.cs
Normal file
62
src/Core/Services/PasswordResetEnrollmentService.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services
|
||||||
|
{
|
||||||
|
public class PasswordResetEnrollmentService : IPasswordResetEnrollmentService
|
||||||
|
{
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
|
||||||
|
public PasswordResetEnrollmentService(IApiService apiService,
|
||||||
|
ICryptoService cryptoService,
|
||||||
|
IOrganizationService organizationService,
|
||||||
|
IStateService stateService)
|
||||||
|
{
|
||||||
|
_apiService = apiService;
|
||||||
|
_cryptoService = cryptoService;
|
||||||
|
_organizationService = organizationService;
|
||||||
|
_stateService = stateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task EnrollIfRequiredAsync(string organizationSsoId)
|
||||||
|
{
|
||||||
|
var orgAutoEnrollStatusResponse = await _apiService.GetOrganizationAutoEnrollStatusAsync(organizationSsoId);
|
||||||
|
|
||||||
|
if (!orgAutoEnrollStatusResponse?.ResetPasswordEnabled ?? false)
|
||||||
|
{
|
||||||
|
await EnrollAsync(orgAutoEnrollStatusResponse.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task EnrollAsync(string organizationId)
|
||||||
|
{
|
||||||
|
var orgKeyResponse = await _apiService.GetOrganizationKeysAsync(organizationId);
|
||||||
|
if (orgKeyResponse == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Organization keys missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId = await _stateService.GetActiveUserIdAsync();
|
||||||
|
var userKey = await _cryptoService.GetUserKeyAsync();
|
||||||
|
var orgPublicKey = CoreHelpers.Base64UrlDecode(orgKeyResponse.PublicKey);
|
||||||
|
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, orgPublicKey);
|
||||||
|
|
||||||
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
|
||||||
|
resetRequest.ResetPasswordKey = encryptedKey.EncryptedString;
|
||||||
|
|
||||||
|
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(
|
||||||
|
organizationId,
|
||||||
|
userId,
|
||||||
|
resetRequest
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ namespace Bit.Core.Utilities
|
|||||||
cryptoFunctionService);
|
cryptoFunctionService);
|
||||||
searchService = new SearchService(cipherService, sendService);
|
searchService = new SearchService(cipherService, sendService);
|
||||||
var policyService = new PolicyService(stateService, organizationService);
|
var policyService = new PolicyService(stateService, organizationService);
|
||||||
var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService,
|
var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService, cryptoFunctionService,
|
||||||
organizationService);
|
organizationService);
|
||||||
var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService,
|
var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService,
|
||||||
folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
||||||
@@ -78,9 +78,10 @@ namespace Bit.Core.Utilities
|
|||||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
|
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
|
||||||
var totpService = new TotpService(cryptoFunctionService);
|
var totpService = new TotpService(cryptoFunctionService);
|
||||||
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
|
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
|
||||||
|
var passwordResetEnrollmentService = new PasswordResetEnrollmentService(apiService, cryptoService, organizationService, stateService);
|
||||||
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
|
||||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||||
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService);
|
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService, passwordResetEnrollmentService);
|
||||||
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
||||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||||
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
|
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
|
||||||
@@ -116,6 +117,7 @@ namespace Bit.Core.Utilities
|
|||||||
Register<IUsernameGenerationService>(usernameGenerationService);
|
Register<IUsernameGenerationService>(usernameGenerationService);
|
||||||
Register<IConfigService>(configService);
|
Register<IConfigService>(configService);
|
||||||
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
|
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
|
||||||
|
Register<IPasswordResetEnrollmentService>(passwordResetEnrollmentService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Register<T>(string serviceName, T obj)
|
public static void Register<T>(string serviceName, T obj)
|
||||||
|
|||||||
Reference in New Issue
Block a user