1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-15 07:43:37 +00:00

[PM-2297] Login with trusted device (Flow 2) (#2623)

* [PM-2297] Add DecryptUserKeyWithDeviceKey method

* [PM-2297] Add methods to DeviceTrustCryptoService update decryption options model

* [PM-2297] Update account decryption options model

* [PM-2297] Fix TrustedDeviceOption and DeviceResponse model. Change StateService device key get set to have default user id

* [PM-2297] Update navigation to decryption options

* [PM-2297] Add missing action navigations to iOS extensions

* [PM-2297] Fix trust device bug/typo

* [PM-2297] Fix model bug

* [PM-2297] Fix state var crash

* [PM-2297] Add trust device login logic to auth service

* [PM-2297] Refactor auth service key connector code

* [PM-2297] Remove reconciledOptions for deviceKey in state service

* [PM-2297] Remove unnecessary user id params
This commit is contained in:
André Bispo
2023-07-27 16:55:06 +01:00
committed by GitHub
parent 080aabfe82
commit dfc7c55b77
14 changed files with 182 additions and 43 deletions

View File

@@ -19,7 +19,7 @@ namespace Bit.App.Pages
{ {
InitializeComponent(); InitializeComponent();
_vm = BindingContext as LoginApproveDeviceViewModel; _vm = BindingContext as LoginApproveDeviceViewModel;
_vm.LogInWithMasterPassword = () => StartLogInWithMasterPassword().FireAndForget(); _vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPassword().FireAndForget();
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget(); _vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget(); _vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
_vm.CloseAction = () => { Navigation.PopModalAsync(); }; _vm.CloseAction = () => { Navigation.PopModalAsync(); };

View File

@@ -32,7 +32,7 @@ namespace Bit.App.Pages
public ICommand ApproveWithMasterPasswordCommand { get; } public ICommand ApproveWithMasterPasswordCommand { get; }
public ICommand ContinueCommand { get; } public ICommand ContinueCommand { get; }
public Action LogInWithMasterPassword { 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 CloseAction { get; set; } public Action CloseAction { get; set; }
@@ -53,7 +53,7 @@ namespace Bit.App.Pages
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPassword), ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction),
onException: ex => HandleException(ex), onException: ex => HandleException(ex),
allowsMultipleExecutions: false); allowsMultipleExecutions: false);
@@ -111,15 +111,7 @@ namespace Bit.App.Pages
var decryptOptions = await _stateService.GetAccountDecryptionOptions(); var decryptOptions = await _stateService.GetAccountDecryptionOptions();
RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false; RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false; ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
} ApproveWithMyOtherDeviceEnabled = decryptOptions?.TrustedDeviceOption?.HasLoginApprovingDevice ?? false;
catch (Exception ex)
{
HandleException(ex);
}
try
{
ApproveWithMyOtherDeviceEnabled = await _apiService.GetDevicesExistenceByTypes(DeviceTypeExtensions.GetDesktopAndMobileTypes().ToArray());
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -29,6 +29,8 @@ namespace Bit.App.Pages
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync()); _vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
_vm.UpdateTempPasswordAction = _vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync()); () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.StartDeviceApprovalOptionsAction =
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
_vm.CloseAction = async () => _vm.CloseAction = async () =>
{ {
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
@@ -106,15 +108,17 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
private async Task StartDeviceApprovalOptionsAsync()
{
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task SsoAuthSuccessAsync() private async Task SsoAuthSuccessAsync()
{ {
RestoreAppOptionsFromCopy(); RestoreAppOptionsFromCopy();
await AppHelpers.ClearPreviousPage(); await AppHelpers.ClearPreviousPage();
// Just for testing the screen
Application.Current.MainPage = new NavigationPage(new LoginApproveDevicePage(_appOptions));
return;
if (await _vaultTimeoutService.IsLockedAsync()) if (await _vaultTimeoutService.IsLockedAsync())
{ {
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions)); Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));

View File

@@ -9,6 +9,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials; using Xamarin.Essentials;
@@ -29,6 +30,8 @@ namespace Bit.App.Pages
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ICryptoService _cryptoService;
private string _orgIdentifier; private string _orgIdentifier;
@@ -45,7 +48,8 @@ namespace Bit.App.Pages
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
_organizationService = ServiceContainer.Resolve<IOrganizationService>(); _organizationService = ServiceContainer.Resolve<IOrganizationService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
PageTitle = AppResources.Bitwarden; PageTitle = AppResources.Bitwarden;
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false); LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
@@ -61,6 +65,7 @@ namespace Bit.App.Pages
public Action StartTwoFactorAction { get; set; } public Action StartTwoFactorAction { get; set; }
public Action StartSetPasswordAction { get; set; } public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; } public Action SsoAuthSuccessAction { get; set; }
public Action StartDeviceApprovalOptionsAction { get; set; }
public Action CloseAction { get; set; } public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; } public Action UpdateTempPasswordAction { get; set; }
@@ -197,6 +202,7 @@ namespace Bit.App.Pages
try try
{ {
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId); var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier); await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
@@ -212,9 +218,31 @@ namespace Bit.App.Pages
{ {
UpdateTempPasswordAction?.Invoke(); UpdateTempPasswordAction?.Invoke();
} }
else if (decryptOptions?.TrustedDeviceOption != null)
{
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
else else
{ {
var task = Task.Run(async () => await _syncService.FullSyncAsync(true)); StartDeviceApprovalOptionsAction?.Invoke();
}
}
else
{
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke(); SsoAuthSuccessAction?.Invoke();
} }
} }

View File

@@ -10,5 +10,7 @@ namespace Bit.Core.Abstractions
Task<DeviceResponse> TrustDeviceIfNeededAsync(); Task<DeviceResponse> TrustDeviceIfNeededAsync();
Task<bool> GetShouldTrustDeviceAsync(); Task<bool> GetShouldTrustDeviceAsync();
Task SetShouldTrustDeviceAsync(bool value); Task SetShouldTrustDeviceAsync(bool value);
Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey);
Task<bool> IsDeviceTrustedAsync();
} }
} }

View File

@@ -11,11 +11,15 @@ namespace Bit.Core.Models.Domain
public class TrustedDeviceOption public class TrustedDeviceOption
{ {
public bool HasAdminApproval { get; set; } public bool HasAdminApproval { get; set; }
public bool HasLoginApprovingDevice { get; set; }
public bool HasManageResetPasswordPermission { get; set; }
public string EncryptedPrivateKey { get; set; }
public string EncryptedUserKey { get; set; }
} }
public class KeyConnectorOption public class KeyConnectorOption
{ {
public bool KeyConnectorUrl { get; set; } public string KeyConnectorUrl { get; set; }
} }
} }

View File

@@ -3,7 +3,7 @@
public class DeviceResponse public class DeviceResponse
{ {
public string Id { get; set; } public string Id { get; set; }
public int Name { get; set; } public string Name { get; set; }
public string Identifier { get; set; } public string Identifier { get; set; }
public DeviceType Type { get; set; } public DeviceType Type { get; set; }
public string CreationDate { get; set; } public string CreationDate { get; set; }

View File

@@ -27,6 +27,7 @@ namespace Bit.Core.Services
private readonly IKeyConnectorService _keyConnectorService; private readonly IKeyConnectorService _keyConnectorService;
private readonly IPasswordGenerationService _passwordGenerationService; private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly bool _setCryptoKeys; private readonly bool _setCryptoKeys;
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>(); private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
@@ -50,6 +51,7 @@ namespace Bit.Core.Services
IKeyConnectorService keyConnectorService, IKeyConnectorService keyConnectorService,
IPasswordGenerationService passwordGenerationService, IPasswordGenerationService passwordGenerationService,
IPolicyService policyService, IPolicyService policyService,
IDeviceTrustCryptoService deviceTrustCryptoService,
bool setCryptoKeys = true) bool setCryptoKeys = true)
{ {
_cryptoService = cryptoService; _cryptoService = cryptoService;
@@ -64,6 +66,7 @@ namespace Bit.Core.Services
_keyConnectorService = keyConnectorService; _keyConnectorService = keyConnectorService;
_passwordGenerationService = passwordGenerationService; _passwordGenerationService = passwordGenerationService;
_policyService = policyService; _policyService = policyService;
_deviceTrustCryptoService = deviceTrustCryptoService;
_setCryptoKeys = setCryptoKeys; _setCryptoKeys = setCryptoKeys;
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>(); TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
@@ -479,19 +482,28 @@ namespace Bit.Core.Services
if (code == null || tokenResponse.Key != null) if (code == null || tokenResponse.Key != null)
{ {
if (tokenResponse.KeyConnectorUrl != null) var decryptOptions = await _stateService.GetAccountDecryptionOptions();
{
await _keyConnectorService.GetAndSetKey(tokenResponse.KeyConnectorUrl);
}
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key); 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))
{
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
if (masterKey != null) if (masterKey != null)
{ {
await _cryptoService.SetMasterKeyAsync(masterKey); await _cryptoService.SetMasterKeyAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey); var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey); await _cryptoService.SetUserKeyAsync(userKey);
} }
}
// User doesn't have a key pair yet (old account), let's generate one for them. // User doesn't have a key pair yet (old account), let's generate one for them.
if (tokenResponse.PrivateKey == null) if (tokenResponse.PrivateKey == null)

View File

@@ -59,7 +59,7 @@ namespace Bit.Core.Services
var deviceIdentifier = await _appIdService.GetAppIdAsync(); var deviceIdentifier = await _appIdService.GetAppIdAsync();
var deviceRequest = new TrustedDeviceKeysRequest var deviceRequest = new TrustedDeviceKeysRequest
{ {
EncryptedUserKey = (await _cryptoService.RsaEncryptAsync(userKey.EncKey, devicePublicKey)).EncryptedString, EncryptedUserKey = (await _cryptoService.RsaEncryptAsync(userKey.Key, devicePublicKey)).EncryptedString,
EncryptedPublicKey = (await _cryptoService.EncryptAsync(devicePublicKey, userKey)).EncryptedString, EncryptedPublicKey = (await _cryptoService.EncryptAsync(devicePublicKey, userKey)).EncryptedString,
EncryptedPrivateKey = (await _cryptoService.EncryptAsync(devicePrivateKey, deviceKey)).EncryptedString, EncryptedPrivateKey = (await _cryptoService.EncryptAsync(devicePrivateKey, deviceKey)).EncryptedString,
}; };
@@ -99,5 +99,31 @@ namespace Bit.Core.Services
await SetShouldTrustDeviceAsync(false); await SetShouldTrustDeviceAsync(false);
return response; return response;
} }
public async Task<bool> IsDeviceTrustedAsync()
{
var existingDeviceKey = await GetDeviceKeyAsync();
return existingDeviceKey != null;
}
public async Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey)
{
var existingDeviceKey = await GetDeviceKeyAsync();
if (existingDeviceKey == null)
{
// User doesn't have a device key anymore so device is untrusted
return null;
}
// Attempt to decrypt encryptedDevicePrivateKey with device key
var devicePrivateKeyBytes = await _cryptoService.DecryptToBytesAsync(
new EncString(encryptedDevicePrivateKey),
existingDeviceKey
);
// Attempt to decrypt encryptedUserDataKey with devicePrivateKey
var userKeyBytes = await _cryptoService.RsaDecryptAsync(encryptedUserKey, devicePrivateKeyBytes);
return new UserKey(userKeyBytes);
}
} }
} }

View File

@@ -515,13 +515,21 @@ namespace Bit.Core.Services
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null) public async Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null)
{ {
var deviceKeyB64 = await _storageMediatorService.GetAsync<string>(Constants.DeviceKeyKey(userId), true); var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
var deviceKeyB64 = await _storageMediatorService.GetAsync<string>(Constants.DeviceKeyKey(reconciledOptions.UserId), true);
if (string.IsNullOrEmpty(deviceKeyB64))
{
return null;
}
return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64)); return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64));
} }
public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null) public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null)
{ {
await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(userId), value.KeyB64, true); var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(reconciledOptions.UserId), value.KeyB64, true);
} }
public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null) public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)

View File

@@ -77,9 +77,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 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); keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService);
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);
@@ -88,7 +89,6 @@ namespace Bit.Core.Utilities
cryptoService); cryptoService);
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService); var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
var configService = new ConfigService(apiService, stateService, logger); var configService = new ConfigService(apiService, stateService, logger);
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
Register<IConditionedAwaiterManager>(conditionedRunner); Register<IConditionedAwaiterManager>(conditionedRunner);
Register<ITokenService>("tokenService", tokenService); Register<ITokenService>("tokenService", tokenService);

View File

@@ -498,7 +498,7 @@ namespace Bit.iOS.Autofill
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false)); vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow()); vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email)); vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
vm.LogInSuccessAction = () => DismissLockAndContinue(); vm.LogInSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
} }
@@ -511,11 +511,11 @@ namespace Bit.iOS.Autofill
LogoutIfAuthed(); LogoutIfAuthed();
} }
private void LaunchLoginWithDevice(string email = null) private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null)
{ {
var appOptions = new AppOptions { IosExtension = true }; var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions); var app = new App.App(appOptions);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, appOptions); var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions);
ThemeManager.SetTheme(app.Resources); ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(loginWithDevicePage); ThemeManager.ApplyResourcesTo(loginWithDevicePage);
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
@@ -545,6 +545,7 @@ namespace Bit.iOS.Autofill
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true)); vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
vm.SsoAuthSuccessAction = () => DismissLockAndContinue(); vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
} }
@@ -621,6 +622,26 @@ namespace Bit.iOS.Autofill
PresentViewController(updateTempPasswordController, true, null); PresentViewController(updateTempPasswordController, true, null);
} }
private void LaunchDeviceApprovalOptionsFlow()
{
var loginApproveDevicePage = new LoginApproveDevicePage();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
{
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(loginApproveDevicePage);
var loginApproveDeviceController = navigationPage.CreateViewController();
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginApproveDeviceController, true, null);
}
public Task SetPreviousPageInfoAsync() => Task.CompletedTask; public Task SetPreviousPageInfoAsync() => Task.CompletedTask;
public Task UpdateThemeAsync() => Task.CompletedTask; public Task UpdateThemeAsync() => Task.CompletedTask;

View File

@@ -520,7 +520,7 @@ namespace Bit.iOS.Extension
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false)); vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow()); vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(email)); vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
vm.LogInSuccessAction = () => DismissLockAndContinue(); vm.LogInSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
} }
@@ -533,11 +533,11 @@ namespace Bit.iOS.Extension
LogoutIfAuthed(); LogoutIfAuthed();
} }
private void LaunchLoginWithDevice(string email = null) private void LaunchLoginWithDevice(AuthRequestType authRequestType,string email = null)
{ {
var appOptions = new AppOptions { IosExtension = true }; var appOptions = new AppOptions { IosExtension = true };
var app = new App.App(appOptions); var app = new App.App(appOptions);
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, appOptions); var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, appOptions);
ThemeManager.SetTheme(app.Resources); ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(loginWithDevicePage); ThemeManager.ApplyResourcesTo(loginWithDevicePage);
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
@@ -567,6 +567,7 @@ namespace Bit.iOS.Extension
vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true)); vm.StartTwoFactorAction = () => DismissViewController(false, () => LaunchTwoFactorFlow(true));
vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissViewController(false, () => LaunchSetPasswordFlow());
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
vm.SsoAuthSuccessAction = () => DismissLockAndContinue(); vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage()); vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
} }
@@ -642,5 +643,25 @@ namespace Bit.iOS.Extension
updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; updateTempPasswordController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(updateTempPasswordController, true, null); PresentViewController(updateTempPasswordController, true, null);
} }
private void LaunchDeviceApprovalOptionsFlow()
{
var loginApproveDevicePage = new LoginApproveDevicePage();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
{
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(loginApproveDevicePage);
var loginApproveDeviceController = navigationPage.CreateViewController();
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginApproveDeviceController, true, null);
}
} }
} }

View File

@@ -339,7 +339,7 @@ namespace Bit.iOS.ShareExtension
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false)); vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow()); vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(email)); vm.LogInWithDeviceAction = () => DismissAndLaunch(() => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, email));
vm.LogInSuccessAction = () => { DismissLockAndContinue(); }; vm.LogInSuccessAction = () => { DismissLockAndContinue(); };
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage()); vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
} }
@@ -348,9 +348,9 @@ namespace Bit.iOS.ShareExtension
LogoutIfAuthed(); LogoutIfAuthed();
} }
private void LaunchLoginWithDevice(string email = null) private void LaunchLoginWithDevice(AuthRequestType authRequestType, string email = null)
{ {
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, _appOptions.Value); var loginWithDevicePage = new LoginPasswordlessRequestPage(email, authRequestType, _appOptions.Value);
SetupAppAndApplyResources(loginWithDevicePage); SetupAppAndApplyResources(loginWithDevicePage);
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm) if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
{ {
@@ -373,6 +373,7 @@ namespace Bit.iOS.ShareExtension
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true)); vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(true));
vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow()); vm.StartSetPasswordAction = () => DismissAndLaunch(() => LaunchSetPasswordFlow());
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow()); vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
vm.StartDeviceApprovalOptionsAction = () => DismissViewController(false, () => LaunchDeviceApprovalOptionsFlow());
vm.SsoAuthSuccessAction = () => DismissLockAndContinue(); vm.SsoAuthSuccessAction = () => DismissLockAndContinue();
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage()); vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage());
} }
@@ -427,6 +428,26 @@ namespace Bit.iOS.ShareExtension
NavigateToPage(updateTempPasswordPage); NavigateToPage(updateTempPasswordPage);
} }
private void LaunchDeviceApprovalOptionsFlow()
{
var loginApproveDevicePage = new LoginApproveDevicePage();
var app = new App.App(new AppOptions { IosExtension = true });
ThemeManager.SetTheme(app.Resources);
ThemeManager.ApplyResourcesTo(loginApproveDevicePage);
if (loginApproveDevicePage.BindingContext is LoginApproveDeviceViewModel vm)
{
vm.LogInWithMasterPasswordAction = () => DismissViewController(false, () => PerformSegue("lockPasswordSegue", this));
vm.RequestAdminApprovalAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AdminApproval, vm.Email));
vm.LogInWithDeviceAction = () => DismissViewController(false, () => LaunchLoginWithDevice(AuthRequestType.AuthenticateAndUnlock, vm.Email));
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage());
}
var navigationPage = new NavigationPage(loginApproveDevicePage);
var loginApproveDeviceController = navigationPage.CreateViewController();
loginApproveDeviceController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
PresentViewController(loginApproveDeviceController, true, null);
}
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null) public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
{ {
if (ExtNavigationController?.ViewControllers?.Any() ?? false) if (ExtNavigationController?.ViewControllers?.Any() ?? false)