Account Deletion on SSO with CME (#1721)

* WIP Added Verification Code page and a verification flow helper to coordinate things

* Improved Verification Code page verification flow helper and fix some issues, also added flag ApiService to choose whether to logout on Unanuthorized

* Improved Verification Code page UI/UX verification flow helper and fix some issues and made some cleanups

* Fix spelling
This commit is contained in:
Federico Maccaroni 2022-01-24 13:25:46 -03:00 committed by GitHub
parent 5a6aec51f3
commit 4e7ceaf5b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 622 additions and 24 deletions

View file

@ -18,6 +18,8 @@ using Plugin.Fingerprint;
using Xamarin.Android.Net; using Xamarin.Android.Net;
using System.Net.Http; using System.Net.Http;
using System.Net; using System.Net;
using Bit.App.Utilities;
using Bit.App.Pages;
#if !FDROID #if !FDROID
using Android.Gms.Security; using Android.Gms.Security;
#endif #endif
@ -45,6 +47,20 @@ namespace Bit.Droid
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey, ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
Constants.AndroidAllClearCipherCacheKeys); Constants.AndroidAllClearCipherCacheKeys);
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
ServiceContainer.Resolve<IApiService>("apiService"),
ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"));
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"));
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
} }
#if !FDROID #if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat) if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)

View file

@ -20,6 +20,7 @@
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2125" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" /> <PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" /> <PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -414,5 +415,6 @@
<ItemGroup> <ItemGroup>
<None Remove="Behaviors\" /> <None Remove="Behaviors\" />
<None Remove="Xamarin.CommunityToolkit" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -12,21 +13,13 @@ namespace Bit.App.Pages
{ {
public class DeleteAccountViewModel : BaseViewModel public class DeleteAccountViewModel : BaseViewModel
{ {
readonly IApiService _apiService;
readonly IPasswordRepromptService _passwordRepromptService;
readonly IMessagingService _messagingService;
readonly ICryptoService _cryptoService;
readonly IPlatformUtilsService _platformUtilsService; readonly IPlatformUtilsService _platformUtilsService;
readonly IDeviceActionService _deviceActionService; readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
public DeleteAccountViewModel() public DeleteAccountViewModel()
{ {
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _verificationActionsFlowHelper = ServiceContainer.Resolve<IVerificationActionsFlowHelper>("verificationActionsFlowHelper");
PageTitle = AppResources.DeleteAccount; PageTitle = AppResources.DeleteAccount;
} }
@ -42,15 +35,53 @@ namespace Bit.App.Pages
return; return;
} }
var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync(); await _verificationActionsFlowHelper
if (!valid) .Configure(VerificationFlowAction.DeleteAccount,
{ null,
return; AppResources.DeleteAccount,
} true)
.ValidateAndExecuteAsync();
}
catch (System.Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
await _platformUtilsService.ShowDialogAsync(AppResources.AnErrorHasOccurred);
}
}
}
public interface IDeleteAccountActionFlowExecutioner : IActionFlowExecutioner { }
public class DeleteAccountActionFlowExecutioner : IDeleteAccountActionFlowExecutioner
{
readonly IApiService _apiService;
readonly IMessagingService _messagingService;
readonly ICryptoService _cryptoService;
readonly IPlatformUtilsService _platformUtilsService;
readonly IDeviceActionService _deviceActionService;
public DeleteAccountActionFlowExecutioner(IApiService apiService,
IMessagingService messagingService,
ICryptoService cryptoService,
IPlatformUtilsService platformUtilsService,
IDeviceActionService deviceActionService)
{
_apiService = apiService;
_messagingService = messagingService;
_cryptoService = cryptoService;
_platformUtilsService = platformUtilsService;
_deviceActionService = deviceActionService;
}
public async Task Execute(IActionFlowParmeters parameters)
{
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.DeletingYourAccount); await _deviceActionService.ShowLoadingAsync(AppResources.DeletingYourAccount);
var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(password, null); var masterPasswordHashKey = await _cryptoService.HashPasswordAsync(parameters.Secret, null);
await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest await _apiService.DeleteAccountAsync(new Core.Models.Request.DeleteAccountRequest
{ {
MasterPasswordHash = masterPasswordHashKey MasterPasswordHash = masterPasswordHashKey

View file

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.Accounts.VerificationCodePage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:DataType="pages:VerificationCodeViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:VerificationCodeViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<u:InverseBoolConverter x:Key="inverseBool" />
<Style TargetType="Label" x:Key="lblDescription">
<Setter Property="FontSize" Value="{OnPlatform Android=Large, iOS=Small}" />
</Style>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView>
<Grid
RowSpacing="10"
ColumnSpacing="10"
RowDefinitions="Auto, Auto, Auto"
ColumnDefinitions="*, *"
Padding="10">
<Label
Grid.Row="0"
Grid.ColumnSpan="2"
Padding="0,10"
Text="{Binding SendCodeStatus}"
StyleClass="box-label"
LineBreakMode="WordWrap"
Margin="0,0,0,10" />
<Grid
StyleClass="box-row"
Grid.Row="1"
Grid.ColumnSpan="2"
RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="*,Auto"
Padding="0">
<Label
Text="{u:I18n VerificationCode}"
StyleClass="box-label"
Grid.Row="0"
Grid.ColumnSpan="2" />
<controls:MonoEntry
x:Name="_secret"
Text="{Binding Secret}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding MainActionCommand}" />
<controls:FaButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="1"
Grid.Column="1"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"/>
<Label
Text="{u:I18n ConfirmYourIdentity}"
StyleClass="box-footer-label"
LineBreakMode="WordWrap"
Grid.Row="2"
Margin="0,10,0,0" />
</Grid>
<Button
x:Name="_mainActionButton"
Grid.Row="2"
Padding="10,0"
Text="{Binding MainActionText}"
Command="{Binding MainActionCommand}"
HorizontalOptions="FillAndExpand"
VerticalOptions="Start" />
<Button
Grid.Row="2"
Grid.Column="1"
Text="{u:I18n ResendCode}"
Padding="10,0"
Command="{Binding RequestOTPCommand}"
HorizontalOptions="Fill"
VerticalOptions="Start"/>
</Grid>
</ScrollView>
</ContentPage.Content>
</pages:BaseContentPage>

View file

@ -0,0 +1,49 @@
using System;
using System.Runtime.CompilerServices;
namespace Bit.App.Pages.Accounts
{
public partial class VerificationCodePage : BaseContentPage
{
VerificationCodeViewModel _vm;
public VerificationCodePage(string mainActionText, bool mainActionStyleDanger)
{
InitializeComponent();
_vm = BindingContext as VerificationCodeViewModel;
_vm.Page = this;
_vm.MainActionText = mainActionText;
_mainActionButton.StyleClass = new[]
{
mainActionStyleDanger ? "btn-danger" : "btn-primary"
};
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(VerificationCodeViewModel.ShowPassword))
{
RequestFocus(_secret);
}
}
protected async override void OnAppearing()
{
base.OnAppearing();
await _vm.InitAsync();
RequestFocus(_secret);
}
private async void Close_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
await Navigation.PopModalAsync();
}
}
}
}

View file

@ -0,0 +1,176 @@
using System;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Threading.Tasks;
using Bit.Core.Exceptions;
using Xamarin.Forms;
using Xamarin.CommunityToolkit.ObjectModel;
using System.Windows.Input;
using Bit.App.Utilities;
#if !FDROID
using Microsoft.AppCenter.Crashes;
#endif
namespace Bit.App.Pages
{
public class VerificationCodeViewModel : BaseViewModel
{
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IUserVerificationService _userVerificationService;
private readonly IApiService _apiService;
private readonly IVerificationActionsFlowHelper _verificationActionsFlowHelper;
private bool _showPassword;
private string _secret, _mainActionText, _sendCodeStatus;
public VerificationCodeViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_verificationActionsFlowHelper = ServiceContainer.Resolve<IVerificationActionsFlowHelper>("verificationActionsFlowHelper");
PageTitle = AppResources.VerificationCode;
TogglePasswordCommand = new Command(TogglePassword);
MainActionCommand = new AsyncCommand(MainActionAsync, allowsMultipleExecutions: false);
RequestOTPCommand = new AsyncCommand(RequestOTPAsync, allowsMultipleExecutions: false);
}
public bool ShowPassword
{
get => _showPassword;
set => SetProperty(ref _showPassword, value,
additionalPropertyNames: new string[] { nameof(ShowPasswordIcon) });
}
public string Secret
{
get => _secret;
set => SetProperty(ref _secret, value);
}
public string MainActionText
{
get => _mainActionText;
set => SetProperty(ref _mainActionText, value);
}
public string SendCodeStatus
{
get => _sendCodeStatus;
set => SetProperty(ref _sendCodeStatus, value);
}
public ICommand TogglePasswordCommand { get; }
public ICommand MainActionCommand { get; }
public ICommand RequestOTPCommand { get; }
public string ShowPasswordIcon => ShowPassword ? "" : "";
public void TogglePassword() => ShowPassword = !ShowPassword;
public async Task InitAsync()
{
await RequestOTPAsync();
}
public async Task RequestOTPAsync()
{
try
{
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.SendingCode);
await _apiService.PostAccountRequestOTP();
SendCodeStatus = AppResources.AVerificationCodeWasSentToYourEmail;
_platformUtilsService.ShowToast(null, null, AppResources.CodeSent);
}
catch (ApiException e)
{
await _deviceActionService.HideLoadingAsync();
SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(), AppResources.AnErrorHasOccurred);
}
}
catch (Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
await _deviceActionService.HideLoadingAsync();
SendCodeStatus = AppResources.AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain;
}
}
private async Task MainActionAsync()
{
try
{
if (string.IsNullOrWhiteSpace(Secret))
{
await _platformUtilsService.ShowDialogAsync(AppResources.EnterTheVerificationCodeThatWasSentToYourEmail, AppResources.AnErrorHasOccurred);
return;
}
if (Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
await _platformUtilsService.ShowDialogAsync(AppResources.InternetConnectionRequiredMessage,
AppResources.InternetConnectionRequiredTitle, AppResources.Ok);
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.Verifying);
if (!await _userVerificationService.VerifyUser(Secret, Core.Enums.VerificationType.OTP))
{
await _deviceActionService.HideLoadingAsync();
return;
}
await _deviceActionService.HideLoadingAsync();
var parameters = _verificationActionsFlowHelper.GetParameters();
parameters.Secret = Secret;
await _verificationActionsFlowHelper.ExecuteAsync(parameters);
Secret = string.Empty;
}
catch (ApiException e)
{
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
catch (Exception ex)
{
#if !FDROID
Crashes.TrackError(ex);
#endif
await _deviceActionService.HideLoadingAsync();
}
}
}
}

View file

@ -3766,5 +3766,41 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("Sending", resourceCulture); return ResourceManager.GetString("Sending", resourceCulture);
} }
} }
public static string SendingCode {
get {
return ResourceManager.GetString("SendingCode", resourceCulture);
}
}
public static string Verifying {
get {
return ResourceManager.GetString("Verifying", resourceCulture);
}
}
public static string ResendCode {
get {
return ResourceManager.GetString("ResendCode", resourceCulture);
}
}
public static string AVerificationCodeWasSentToYourEmail {
get {
return ResourceManager.GetString("AVerificationCodeWasSentToYourEmail", resourceCulture);
}
}
public static string AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain {
get {
return ResourceManager.GetString("AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain", resourceCulture);
}
}
public static string EnterTheVerificationCodeThatWasSentToYourEmail {
get {
return ResourceManager.GetString("EnterTheVerificationCodeThatWasSentToYourEmail", resourceCulture);
}
}
} }
} }

View file

@ -2117,4 +2117,22 @@
<data name="Sending" xml:space="preserve"> <data name="Sending" xml:space="preserve">
<value>Sending</value> <value>Sending</value>
</data> </data>
<data name="SendingCode" xml:space="preserve">
<value>Sending code</value>
</data>
<data name="Verifying" xml:space="preserve">
<value>Verifying</value>
</data>
<data name="ResendCode" xml:space="preserve">
<value>Resend Code</value>
</data>
<data name="AVerificationCodeWasSentToYourEmail" xml:space="preserve">
<value>A verification code was sent to your email</value>
</data>
<data name="AnErrorOccurredWhileSendingAVerificationCodeToYourEmailPleaseTryAgain" xml:space="preserve">
<value>An error occurred while sending a verification code to your email. Please try again</value>
</data>
<data name="EnterTheVerificationCodeThatWasSentToYourEmail" xml:space="preserve">
<value>Enter the verification code that was sent to your email</value>
</data>
</root> </root>

View file

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Pages;
using Bit.App.Pages.Accounts;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Utilities
{
public interface IVerificationActionsFlowHelper
{
IVerificationActionsFlowHelper Configure(VerificationFlowAction action,
IActionFlowParmeters parameters = null,
string verificatioCodeMainActionText = null,
bool isVerificationCodeMainActionStyleDanger = false);
IActionFlowParmeters GetParameters();
Task ValidateAndExecuteAsync();
Task ExecuteAsync(IActionFlowParmeters parameters);
}
public interface IActionFlowParmeters
{
string Secret { get; set; }
}
public class DefaultActionFlowParameters : IActionFlowParmeters
{
public string Secret { get; set; }
}
public interface IActionFlowExecutioner
{
Task Execute(IActionFlowParmeters parameters);
}
public enum VerificationFlowAction
{
ExportVault,
DeleteAccount
}
/// <summary>
/// Verifies and execute actions on the corresponding order.
///
/// Use: From the caller
/// - Configure it on <see cref="Configure(VerificationFlowAction, IActionFlowParmeters)"/>
/// - Call <see cref="ValidateAndExecuteAsync"/>
///
/// For every <see cref="VerificationFlowAction"/> we need an implementation of <see cref="IActionFlowExecutioner"/>
/// and it to be configured in the inner dictionary.
/// Also, inherit from <see cref="DefaultActionFlowParameters"/> if more custom parameters are needed for the executioner.
/// </summary>
public class VerificationActionsFlowHelper : IVerificationActionsFlowHelper
{
private readonly IKeyConnectorService _keyConnectorService;
private readonly IPasswordRepromptService _passwordRepromptService;
private VerificationFlowAction? _action;
private IActionFlowParmeters _parameters;
private string _verificationCodeMainActionText;
private bool _isVerificationCodeMainActionStyleDanger;
private readonly Dictionary<VerificationFlowAction, IActionFlowExecutioner> _actionExecutionerDictionary = new Dictionary<VerificationFlowAction, IActionFlowExecutioner>();
public VerificationActionsFlowHelper(IKeyConnectorService keyConnectorService,
IPasswordRepromptService passwordRepromptService)
{
_keyConnectorService = keyConnectorService;
_passwordRepromptService = passwordRepromptService;
_actionExecutionerDictionary.Add(VerificationFlowAction.DeleteAccount, ServiceContainer.Resolve<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner"));
}
public IVerificationActionsFlowHelper Configure(VerificationFlowAction action,
IActionFlowParmeters parameters = null,
string verificationCodeMainActionText = null,
bool isVerificationCodeMainActionStyleDanger = false)
{
_action = action;
_parameters = parameters;
_verificationCodeMainActionText = verificationCodeMainActionText;
_isVerificationCodeMainActionStyleDanger = isVerificationCodeMainActionStyleDanger;
return this;
}
public IActionFlowParmeters GetParameters()
{
if (_parameters is null)
{
_parameters = new DefaultActionFlowParameters();
}
return _parameters;
}
public async Task ValidateAndExecuteAsync()
{
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
? VerificationType.OTP
: VerificationType.MasterPassword;
switch (verificationType)
{
case VerificationType.MasterPassword:
var (password, valid) = await _passwordRepromptService.ShowPasswordPromptAndGetItAsync();
if (!valid)
{
return;
}
GetParameters().Secret = password;
await ExecuteAsync(_parameters);
break;
case VerificationType.OTP:
await Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(
new VerificationCodePage(_verificationCodeMainActionText, _isVerificationCodeMainActionStyleDanger)));
break;
default:
throw new NotImplementedException($"There is no implementation for {verificationType}");
}
}
/// <summary>
/// Executes the action with the given parameters after we have gotten the verification secret
/// </summary>
public async Task ExecuteAsync(IActionFlowParmeters parameters)
{
if (!_action.HasValue)
{
// this should never happen
throw new InvalidOperationException("A problem occurred while getting the action value after validation");
}
if (!_actionExecutionerDictionary.TryGetValue(_action.Value, out var executioner))
{
throw new InvalidOperationException($"There is no executioner for {_action}");
}
await executioner.Execute(GetParameters());
}
}
}

View file

@ -46,7 +46,7 @@ namespace Bit.Core.Abstractions
Task RefreshIdentityTokenAsync(); Task RefreshIdentityTokenAsync();
Task<object> PreValidateSso(string identifier); Task<object> PreValidateSso(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
TRequest body, bool authed, bool hasResponse); TRequest body, bool authed, bool hasResponse, bool logoutOnUnauthorized = true);
void SetUrls(EnvironmentUrls urls); void SetUrls(EnvironmentUrls urls);
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")] [Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data); Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);

View file

@ -180,13 +180,13 @@ namespace Bit.Core.Services
public Task PostAccountRequestOTP() public Task PostAccountRequestOTP()
{ {
return SendAsync<object, object>(HttpMethod.Post, "/accounts/request-otp", null, true, false); return SendAsync<object, object>(HttpMethod.Post, "/accounts/request-otp", null, true, false, false);
} }
public Task PostAccountVerifyOTPAsync(VerifyOTPRequest request) public Task PostAccountVerifyOTPAsync(VerifyOTPRequest request)
{ {
return SendAsync<VerifyOTPRequest, object>(HttpMethod.Post, "/accounts/verify-otp", request, return SendAsync<VerifyOTPRequest, object>(HttpMethod.Post, "/accounts/verify-otp", request,
true, false); true, false, false);
} }
public Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request) public Task PutUpdateTempPasswordAsync(UpdateTempPasswordRequest request)
@ -579,7 +579,7 @@ namespace Bit.Core.Services
public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) => public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) =>
SendAsync<object, TResponse>(method, path, null, authed, true); SendAsync<object, TResponse>(method, path, null, authed, true);
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body, public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
bool authed, bool hasResponse) bool authed, bool hasResponse, bool logoutOnUnauthorized = true)
{ {
using (var requestMessage = new HttpRequestMessage()) using (var requestMessage = new HttpRequestMessage())
{ {
@ -631,7 +631,7 @@ namespace Bit.Core.Services
} }
else if (!response.IsSuccessStatusCode) else if (!response.IsSuccessStatusCode)
{ {
var error = await HandleErrorAsync(response, false, authed); var error = await HandleErrorAsync(response, false, authed, logoutOnUnauthorized);
throw new ApiException(error); throw new ApiException(error);
} }
return (TResponse)(object)null; return (TResponse)(object)null;
@ -693,10 +693,18 @@ namespace Bit.Core.Services
}; };
} }
private async Task<ErrorResponse> HandleErrorAsync(HttpResponseMessage response, bool tokenError, bool authed) private async Task<ErrorResponse> HandleErrorAsync(HttpResponseMessage response, bool tokenError,
bool authed, bool logoutOnUnauthorized = true)
{ {
if (authed && ((tokenError && response.StatusCode == HttpStatusCode.BadRequest) || if (authed
response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)) &&
(
(tokenError && response.StatusCode == HttpStatusCode.BadRequest)
||
(logoutOnUnauthorized && response.StatusCode == HttpStatusCode.Unauthorized)
||
response.StatusCode == HttpStatusCode.Forbidden
))
{ {
await _logoutCallbackAsync(true); await _logoutCallbackAsync(true);
return null; return null;

View file

@ -3,6 +3,7 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Pages;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Services; using Bit.App.Services;
using Bit.App.Utilities; using Bit.App.Utilities;
@ -148,6 +149,21 @@ namespace Bit.iOS.Core.Utilities
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync( await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
Bit.Core.Constants.DisableFaviconKey, disableFavicon); Bit.Core.Constants.DisableFaviconKey, disableFavicon);
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync(); await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
ServiceContainer.Resolve<IApiService>("apiService"),
ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"));
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"));
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
if (postBootstrapFunc != null) if (postBootstrapFunc != null)
{ {
await postBootstrapFunc.Invoke(); await postBootstrapFunc.Invoke();