mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +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()
|
||||
{
|
||||
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))
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
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>
|
||||
/// Looks up a localized string similar to Login attempt by {0} on {1}.
|
||||
/// </summary>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Bit.Core.Abstractions
|
||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode);
|
||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);
|
||||
Task CreateNewSsoUserAsync(string organizationSsoId);
|
||||
|
||||
void LogOut(Action callback);
|
||||
void Init();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Response;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
@@ -10,7 +11,8 @@ namespace Bit.Core.Abstractions
|
||||
Task<bool> GetUsesKeyConnector();
|
||||
Task<bool> UserNeedsMigration();
|
||||
Task MigrateUser();
|
||||
Task GetAndSetKey(string url);
|
||||
Task GetAndSetKeyAsync(string url);
|
||||
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 TrustedDeviceOption TrustedDeviceOption { get; set; }
|
||||
public KeyConnectorOption KeyConnectorOption { get; set; }
|
||||
|
||||
public bool RequireSetPassword => !HasMasterPassword && KeyConnectorOption == null;
|
||||
}
|
||||
|
||||
public class TrustedDeviceOption
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Bit.Core.Services
|
||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||
private readonly IPasswordResetEnrollmentService _passwordResetEnrollmentService;
|
||||
private readonly bool _setCryptoKeys;
|
||||
|
||||
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
|
||||
@@ -52,6 +53,7 @@ namespace Bit.Core.Services
|
||||
IPasswordGenerationService passwordGenerationService,
|
||||
IPolicyService policyService,
|
||||
IDeviceTrustCryptoService deviceTrustCryptoService,
|
||||
IPasswordResetEnrollmentService passwordResetEnrollmentService,
|
||||
bool setCryptoKeys = true)
|
||||
{
|
||||
_cryptoService = cryptoService;
|
||||
@@ -67,6 +69,7 @@ namespace Bit.Core.Services
|
||||
_passwordGenerationService = passwordGenerationService;
|
||||
_policyService = policyService;
|
||||
_deviceTrustCryptoService = deviceTrustCryptoService;
|
||||
_passwordResetEnrollmentService = passwordResetEnrollmentService;
|
||||
_setCryptoKeys = setCryptoKeys;
|
||||
|
||||
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||
@@ -505,20 +508,21 @@ namespace Bit.Core.Services
|
||||
await _cryptoService.SetUserKeyAsync(userKey);
|
||||
}
|
||||
|
||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||
var hasUserKey = await _cryptoService.HasUserKeyAsync();
|
||||
if (decryptOptions?.TrustedDeviceOption != null && !hasUserKey)
|
||||
{
|
||||
var key = await _deviceTrustCryptoService.DecryptUserKeyWithDeviceKeyAsync(decryptOptions.TrustedDeviceOption.EncryptedPrivateKey, decryptOptions.TrustedDeviceOption.EncryptedUserKey);
|
||||
if (key != null)
|
||||
{
|
||||
await _cryptoService.SetUserKeyAsync(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (code == null || tokenResponse.Key != null)
|
||||
{
|
||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
||||
|
||||
if (decryptOptions?.TrustedDeviceOption != null)
|
||||
{
|
||||
var key = await _deviceTrustCryptoService.DecryptUserKeyWithDeviceKeyAsync(decryptOptions.TrustedDeviceOption.EncryptedPrivateKey, decryptOptions.TrustedDeviceOption.EncryptedUserKey);
|
||||
if (key != null)
|
||||
{
|
||||
await _cryptoService.SetUserKeyAsync(key);
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
|
||||
if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
|
||||
{
|
||||
|
||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
||||
@@ -558,37 +562,15 @@ namespace Bit.Core.Services
|
||||
}
|
||||
else if (tokenResponse.KeyConnectorUrl != null)
|
||||
{
|
||||
// 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);
|
||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync();
|
||||
|
||||
try
|
||||
// New User has tokenResponse.Key == null
|
||||
if (tokenResponse.Key == null)
|
||||
{
|
||||
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)));
|
||||
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.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@@ -12,19 +13,21 @@ namespace Bit.Core.Services
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IApiService _apiService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
|
||||
public KeyConnectorService(IStateService stateService, ICryptoService cryptoService,
|
||||
ITokenService tokenService, IApiService apiService, OrganizationService organizationService)
|
||||
ITokenService tokenService, IApiService apiService, ICryptoFunctionService cryptoFunctionService, OrganizationService organizationService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_cryptoService = cryptoService;
|
||||
_tokenService = tokenService;
|
||||
_apiService = apiService;
|
||||
_cryptoFunctionService = cryptoFunctionService;
|
||||
_organizationService = organizationService;
|
||||
}
|
||||
|
||||
public async Task GetAndSetKey(string url)
|
||||
public async Task GetAndSetKeyAsync(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -83,5 +86,40 @@ namespace Bit.Core.Services
|
||||
|
||||
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);
|
||||
searchService = new SearchService(cipherService, sendService);
|
||||
var policyService = new PolicyService(stateService, organizationService);
|
||||
var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService,
|
||||
var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService, cryptoFunctionService,
|
||||
organizationService);
|
||||
var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService,
|
||||
folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
||||
@@ -78,9 +78,10 @@ namespace Bit.Core.Utilities
|
||||
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
|
||||
var totpService = new TotpService(cryptoFunctionService);
|
||||
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,
|
||||
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService);
|
||||
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService, passwordResetEnrollmentService);
|
||||
var exportService = new ExportService(folderService, cipherService, cryptoService);
|
||||
var auditService = new AuditService(cryptoFunctionService, apiService);
|
||||
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
|
||||
@@ -116,6 +117,7 @@ namespace Bit.Core.Utilities
|
||||
Register<IUsernameGenerationService>(usernameGenerationService);
|
||||
Register<IConfigService>(configService);
|
||||
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
|
||||
Register<IPasswordResetEnrollmentService>(passwordResetEnrollmentService);
|
||||
}
|
||||
|
||||
public static void Register<T>(string serviceName, T obj)
|
||||
|
||||
Reference in New Issue
Block a user