mirror of
https://github.com/bitwarden/android.git
synced 2024-12-26 10:58:29 +03:00
[PM-2671] Update mobile client to use regions (#2798)
* [PM-2671] Update mobile client to use regions * [PM-2671] Refactor * [PM-2671] Move migration of region to migration service. * [PM-2671] Move comment * [PM-2671] Change method name * [PM-2671] Change method name on usages --------- Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
This commit is contained in:
parent
7a65bf7fd7
commit
9506595fdd
20 changed files with 294 additions and 134 deletions
|
@ -3,7 +3,7 @@ using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
|
||||||
|
@ -19,14 +19,25 @@ namespace Bit.App.Pages
|
||||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||||
|
|
||||||
PageTitle = AppResources.Settings;
|
PageTitle = AppResources.Settings;
|
||||||
BaseUrl = _environmentService.BaseUrl == EnvironmentUrlData.DefaultEU.Base || EnvironmentUrlData.DefaultUS.Base == _environmentService.BaseUrl ?
|
SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||||
string.Empty : _environmentService.BaseUrl;
|
Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
if (_environmentService.SelectedRegion != Region.SelfHosted ||
|
||||||
|
_environmentService.BaseUrl == Region.US.BaseUrl() ||
|
||||||
|
_environmentService.BaseUrl == Region.EU.BaseUrl())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseUrl = _environmentService.BaseUrl;
|
||||||
WebVaultUrl = _environmentService.WebVaultUrl;
|
WebVaultUrl = _environmentService.WebVaultUrl;
|
||||||
ApiUrl = _environmentService.ApiUrl;
|
ApiUrl = _environmentService.ApiUrl;
|
||||||
IdentityUrl = _environmentService.IdentityUrl;
|
IdentityUrl = _environmentService.IdentityUrl;
|
||||||
IconsUrl = _environmentService.IconsUrl;
|
IconsUrl = _environmentService.IconsUrl;
|
||||||
NotificationsUrls = _environmentService.NotificationsUrl;
|
NotificationsUrls = _environmentService.NotificationsUrl;
|
||||||
SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommand SubmitCommand { get; }
|
public ICommand SubmitCommand { get; }
|
||||||
|
@ -46,8 +57,7 @@ namespace Bit.App.Pages
|
||||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.EnvironmentPageUrlsError, AppResources.Ok);
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.EnvironmentPageUrlsError, AppResources.Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var urls = new Core.Models.Data.EnvironmentUrlData
|
||||||
var resUrls = await _environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
|
|
||||||
{
|
{
|
||||||
Base = BaseUrl,
|
Base = BaseUrl,
|
||||||
Api = ApiUrl,
|
Api = ApiUrl,
|
||||||
|
@ -55,7 +65,8 @@ namespace Bit.App.Pages
|
||||||
WebVault = WebVaultUrl,
|
WebVault = WebVaultUrl,
|
||||||
Icons = IconsUrl,
|
Icons = IconsUrl,
|
||||||
Notifications = NotificationsUrls
|
Notifications = NotificationsUrls
|
||||||
});
|
};
|
||||||
|
var resUrls = await _environmentService.SetRegionAsync(urls.Region, urls);
|
||||||
|
|
||||||
// re-set urls since service can change them, ex: prefixing https://
|
// re-set urls since service can change them, ex: prefixing https://
|
||||||
BaseUrl = resUrls.Base;
|
BaseUrl = resUrls.Base;
|
||||||
|
|
|
@ -74,7 +74,7 @@ namespace Bit.App.Pages
|
||||||
});
|
});
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _vm.UpdateEnvironment();
|
await _vm.UpdateEnvironmentAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Styles;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
@ -10,13 +11,12 @@ using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
using BwRegion = Bit.Core.Enums.Region;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class HomeViewModel : BaseViewModel
|
public class HomeViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private const string LOGGING_IN_ON_US = "bitwarden.com";
|
|
||||||
private const string LOGGING_IN_ON_EU = "bitwarden.eu";
|
|
||||||
|
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
|
@ -165,8 +165,8 @@ namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag);
|
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag);
|
||||||
var options = _displayEuEnvironment
|
var options = _displayEuEnvironment
|
||||||
? new string[] { LOGGING_IN_ON_US, LOGGING_IN_ON_EU, AppResources.SelfHosted }
|
? new string[] { BwRegion.US.Domain(), BwRegion.EU.Domain(), AppResources.SelfHosted }
|
||||||
: new string[] { LOGGING_IN_ON_US, AppResources.SelfHosted };
|
: new string[] { BwRegion.US.Domain(), AppResources.SelfHosted };
|
||||||
|
|
||||||
await Device.InvokeOnMainThreadAsync(async () =>
|
await Device.InvokeOnMainThreadAsync(async () =>
|
||||||
{
|
{
|
||||||
|
@ -183,35 +183,23 @@ namespace Bit.App.Pages
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _environmentService.SetUrlsAsync(result == LOGGING_IN_ON_EU ? EnvironmentUrlData.DefaultEU : EnvironmentUrlData.DefaultUS);
|
await _environmentService.SetRegionAsync(result == BwRegion.EU.Domain() ? BwRegion.EU : BwRegion.US);
|
||||||
await _configService.GetAsync(true);
|
await _configService.GetAsync(true);
|
||||||
SelectedEnvironmentName = result;
|
SelectedEnvironmentName = result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateEnvironment()
|
public async Task UpdateEnvironmentAsync()
|
||||||
{
|
{
|
||||||
var environmentsSaved = await _stateService.GetPreAuthEnvironmentUrlsAsync();
|
var region = _environmentService.SelectedRegion;
|
||||||
if (environmentsSaved == null || environmentsSaved.IsEmpty)
|
if (region == BwRegion.SelfHosted)
|
||||||
{
|
{
|
||||||
await _environmentService.SetUrlsAsync(EnvironmentUrlData.DefaultUS);
|
SelectedEnvironmentName = AppResources.SelfHosted;
|
||||||
environmentsSaved = EnvironmentUrlData.DefaultUS;
|
await _configService.GetAsync(true);
|
||||||
SelectedEnvironmentName = LOGGING_IN_ON_US;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (environmentsSaved.Base == EnvironmentUrlData.DefaultUS.Base)
|
|
||||||
{
|
|
||||||
SelectedEnvironmentName = LOGGING_IN_ON_US;
|
|
||||||
}
|
|
||||||
else if (environmentsSaved.Base == EnvironmentUrlData.DefaultEU.Base)
|
|
||||||
{
|
|
||||||
SelectedEnvironmentName = LOGGING_IN_ON_EU;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _configService.GetAsync(true);
|
SelectedEnvironmentName = region.Domain();
|
||||||
SelectedEnvironmentName = AppResources.SelfHosted;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,13 +206,8 @@ namespace Bit.App.Pages
|
||||||
_logger.Exception(new NullReferenceException("Email not found in storage"));
|
_logger.Exception(new NullReferenceException("Email not found in storage"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var webVault = _environmentService.GetWebVaultUrl(true);
|
|
||||||
if (string.IsNullOrWhiteSpace(webVault))
|
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, _environmentService.GetCurrentDomain());
|
||||||
{
|
|
||||||
webVault = "https://bitwarden.com";
|
|
||||||
}
|
|
||||||
var webVaultHostname = CoreHelpers.GetHostname(webVault);
|
|
||||||
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
|
|
||||||
if (PinEnabled)
|
if (PinEnabled)
|
||||||
{
|
{
|
||||||
PageTitle = AppResources.VerifyPIN;
|
PageTitle = AppResources.VerifyPIN;
|
||||||
|
|
|
@ -162,7 +162,7 @@ namespace Bit.App.Pages
|
||||||
Email = await _stateService.GetRememberedEmailAsync();
|
Email = await _stateService.GetRememberedEmailAsync();
|
||||||
}
|
}
|
||||||
CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email;
|
CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email;
|
||||||
EnvironmentDomainName = CoreHelpers.GetDomain((await _stateService.GetPreAuthEnvironmentUrlsAsync())?.Base);
|
EnvironmentDomainName = _environmentService.GetCurrentDomain();
|
||||||
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, await _appIdService.GetAppIdAsync());
|
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, await _appIdService.GetAppIdAsync());
|
||||||
}
|
}
|
||||||
catch (ApiException apiEx) when (apiEx.Error.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
catch (ApiException apiEx) when (apiEx.Error.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
|
|
@ -405,14 +405,15 @@ namespace Bit.App.Utilities
|
||||||
var settingValue = string.IsNullOrWhiteSpace(setting.Value) ? null : setting.Value;
|
var settingValue = string.IsNullOrWhiteSpace(setting.Value) ? null : setting.Value;
|
||||||
if (environmentService.BaseUrl != settingValue)
|
if (environmentService.BaseUrl != settingValue)
|
||||||
{
|
{
|
||||||
await environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
|
var urls = new EnvironmentUrlData
|
||||||
{
|
{
|
||||||
Base = settingValue,
|
Base = settingValue,
|
||||||
Api = environmentService.ApiUrl,
|
Api = environmentService.ApiUrl,
|
||||||
Identity = environmentService.IdentityUrl,
|
Identity = environmentService.IdentityUrl,
|
||||||
WebVault = environmentService.WebVaultUrl,
|
WebVault = environmentService.WebVaultUrl,
|
||||||
Icons = environmentService.IconsUrl
|
Icons = environmentService.IconsUrl
|
||||||
});
|
};
|
||||||
|
await environmentService.SetRegionAsync(urls.Region, urls);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Models.Response;
|
using Bit.Core.Models.Response;
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ namespace Bit.Core.Abstractions
|
||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true, bool sendToIdentity = false);
|
TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true, bool sendToIdentity = false);
|
||||||
Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default);
|
Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrlData 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);
|
||||||
Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request);
|
Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
|
@ -12,10 +13,12 @@ namespace Bit.Core.Abstractions
|
||||||
string NotificationsUrl { get; set; }
|
string NotificationsUrl { get; set; }
|
||||||
string WebVaultUrl { get; set; }
|
string WebVaultUrl { get; set; }
|
||||||
string EventsUrl { get; set; }
|
string EventsUrl { get; set; }
|
||||||
|
Region SelectedRegion { get; set; }
|
||||||
|
|
||||||
string GetWebVaultUrl(bool returnNullIfDefault = false);
|
string GetWebVaultUrl(bool returnNullIfDefault = false);
|
||||||
string GetWebSendUrl();
|
string GetWebSendUrl();
|
||||||
Task<EnvironmentUrlData> SetUrlsAsync(EnvironmentUrlData urls);
|
string GetCurrentDomain();
|
||||||
Task SetUrlsFromStorageAsync();
|
Task SetUrlsFromStorageAsync();
|
||||||
|
Task<EnvironmentUrlData> SetRegionAsync(Region region, EnvironmentUrlData selfHostedUrls = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,10 @@ namespace Bit.Core.Abstractions
|
||||||
void SetConfigs(ConfigResponse value);
|
void SetConfigs(ConfigResponse value);
|
||||||
Task<bool> GetShouldTrustDeviceAsync();
|
Task<bool> GetShouldTrustDeviceAsync();
|
||||||
Task SetShouldTrustDeviceAsync(bool value);
|
Task SetShouldTrustDeviceAsync(bool value);
|
||||||
|
Task<Region?> GetActiveUserRegionAsync();
|
||||||
|
Task<Region?> GetPreAuthRegionAsync();
|
||||||
|
Task SetPreAuthRegionAsync(Region value);
|
||||||
|
|
||||||
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||||
Task<string> GetPinProtectedAsync(string userId = null);
|
Task<string> GetPinProtectedAsync(string userId = null);
|
||||||
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||||
|
|
|
@ -46,6 +46,7 @@ namespace Bit.Core
|
||||||
public const string PreLoginEmailKey = "preLoginEmailKey";
|
public const string PreLoginEmailKey = "preLoginEmailKey";
|
||||||
public const string ConfigsKey = "configsKey";
|
public const string ConfigsKey = "configsKey";
|
||||||
public const string DisplayEuEnvironmentFlag = "display-eu-environment";
|
public const string DisplayEuEnvironmentFlag = "display-eu-environment";
|
||||||
|
public const string RegionEnvironment = "regionEnvironment";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in
|
/// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in
|
||||||
|
|
10
src/Core/Enums/Region.cs
Normal file
10
src/Core/Enums/Region.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum Region
|
||||||
|
{
|
||||||
|
US,
|
||||||
|
EU,
|
||||||
|
SelfHosted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,34 @@
|
||||||
namespace Bit.Core.Models.Data
|
using System.Text.RegularExpressions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Data
|
||||||
{
|
{
|
||||||
public class EnvironmentUrlData
|
public class EnvironmentUrlData
|
||||||
{
|
{
|
||||||
public static EnvironmentUrlData DefaultUS = new EnvironmentUrlData { Base = "https://vault.bitwarden.com" };
|
public static EnvironmentUrlData DefaultUS = new EnvironmentUrlData
|
||||||
public static EnvironmentUrlData DefaultEU = new EnvironmentUrlData { Base = "https://vault.bitwarden.eu" };
|
{
|
||||||
|
Base = "https://vault.bitwarden.com",
|
||||||
|
Api = "https://api.bitwarden.com",
|
||||||
|
Identity = "https://identity.bitwarden.com",
|
||||||
|
Icons = "https://icons.bitwarden.net",
|
||||||
|
WebVault = "https://vault.bitwarden.com",
|
||||||
|
Notifications = "https://notifications.bitwarden.com",
|
||||||
|
Events = "https://events.bitwarden.com",
|
||||||
|
Domain = "bitwarden.com"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static EnvironmentUrlData DefaultEU = new EnvironmentUrlData
|
||||||
|
{
|
||||||
|
Base = "https://vault.bitwarden.eu",
|
||||||
|
Api = "https://api.bitwarden.eu",
|
||||||
|
Identity = "https://identity.bitwarden.eu",
|
||||||
|
Icons = "https://icons.bitwarden.eu",
|
||||||
|
WebVault = "https://vault.bitwarden.eu",
|
||||||
|
Notifications = "https://notifications.bitwarden.eu",
|
||||||
|
Events = "https://events.bitwarden.eu",
|
||||||
|
Domain = "bitwarden.eu"
|
||||||
|
};
|
||||||
|
|
||||||
public string Base { get; set; }
|
public string Base { get; set; }
|
||||||
public string Api { get; set; }
|
public string Api { get; set; }
|
||||||
|
@ -12,6 +37,7 @@
|
||||||
public string Notifications { get; set; }
|
public string Notifications { get; set; }
|
||||||
public string WebVault { get; set; }
|
public string WebVault { get; set; }
|
||||||
public string Events { get; set; }
|
public string Events { get; set; }
|
||||||
|
public string Domain { get; set; }
|
||||||
|
|
||||||
public bool IsEmpty => string.IsNullOrEmpty(Base)
|
public bool IsEmpty => string.IsNullOrEmpty(Base)
|
||||||
&& string.IsNullOrEmpty(Api)
|
&& string.IsNullOrEmpty(Api)
|
||||||
|
@ -20,5 +46,63 @@
|
||||||
&& string.IsNullOrEmpty(Notifications)
|
&& string.IsNullOrEmpty(Notifications)
|
||||||
&& string.IsNullOrEmpty(WebVault)
|
&& string.IsNullOrEmpty(WebVault)
|
||||||
&& string.IsNullOrEmpty(Events);
|
&& string.IsNullOrEmpty(Events);
|
||||||
|
|
||||||
|
public Region Region
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Base == Region.US.BaseUrl())
|
||||||
|
{
|
||||||
|
return Region.US;
|
||||||
|
}
|
||||||
|
if (Base == Region.EU.BaseUrl())
|
||||||
|
{
|
||||||
|
return Region.EU;
|
||||||
|
}
|
||||||
|
return Region.SelfHosted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnvironmentUrlData FormatUrls()
|
||||||
|
{
|
||||||
|
return new EnvironmentUrlData
|
||||||
|
{
|
||||||
|
Base = FormatUrl(Base),
|
||||||
|
Api = FormatUrl(Api),
|
||||||
|
Identity = FormatUrl(Identity),
|
||||||
|
Icons = FormatUrl(Icons),
|
||||||
|
Notifications = FormatUrl(Notifications),
|
||||||
|
WebVault = FormatUrl(WebVault),
|
||||||
|
Events = FormatUrl(Events)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatUrl(string url)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
url = Regex.Replace(url, "\\/+$", string.Empty);
|
||||||
|
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
|
||||||
|
{
|
||||||
|
url = string.Concat("https://", url);
|
||||||
|
}
|
||||||
|
return url.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDomainOrHostname()
|
||||||
|
{
|
||||||
|
var url = WebVault ?? Base ?? Api ?? Identity;
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
if (url.Contains(Region.US.Domain()) || url.Contains(Region.EU.Domain()))
|
||||||
|
{
|
||||||
|
return CoreHelpers.GetDomain(url);
|
||||||
|
}
|
||||||
|
return CoreHelpers.GetHostname(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,12 +102,14 @@ namespace Bit.Core.Models.Domain
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Region = copy.Region;
|
||||||
EnvironmentUrls = copy.EnvironmentUrls;
|
EnvironmentUrls = copy.EnvironmentUrls;
|
||||||
VaultTimeout = copy.VaultTimeout;
|
VaultTimeout = copy.VaultTimeout;
|
||||||
VaultTimeoutAction = copy.VaultTimeoutAction;
|
VaultTimeoutAction = copy.VaultTimeoutAction;
|
||||||
ScreenCaptureAllowed = copy.ScreenCaptureAllowed;
|
ScreenCaptureAllowed = copy.ScreenCaptureAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Region? Region;
|
||||||
public EnvironmentUrlData EnvironmentUrls;
|
public EnvironmentUrlData EnvironmentUrls;
|
||||||
[Obsolete("Feb 10 2023: VaultTimeout has been deprecated in favor of stored prefs to retain value after logout. It remains here to allow for migration during app upgrade.")]
|
[Obsolete("Feb 10 2023: VaultTimeout has been deprecated in favor of stored prefs to retain value after logout. It remains here to allow for migration during app upgrade.")]
|
||||||
public int? VaultTimeout;
|
public int? VaultTimeout;
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace Bit.Core.Models.Domain
|
|
||||||
{
|
|
||||||
public class EnvironmentUrls
|
|
||||||
{
|
|
||||||
public string Base { get; set; }
|
|
||||||
public string Api { get; set; }
|
|
||||||
public string Identity { get; set; }
|
|
||||||
public string Events { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,21 +22,7 @@ namespace Bit.Core.Models.View
|
||||||
Email = a.Profile?.Email;
|
Email = a.Profile?.Email;
|
||||||
Name = a.Profile?.Name;
|
Name = a.Profile?.Name;
|
||||||
AvatarColor = a.Profile?.AvatarColor;
|
AvatarColor = a.Profile?.AvatarColor;
|
||||||
Hostname = ParseEndpoint(a.Settings?.EnvironmentUrls);
|
Hostname = a.Settings?.EnvironmentUrls?.GetDomainOrHostname();
|
||||||
}
|
|
||||||
|
|
||||||
private string ParseEndpoint(EnvironmentUrlData urls)
|
|
||||||
{
|
|
||||||
var url = urls?.WebVault ?? urls?.Base;
|
|
||||||
if (!string.IsNullOrWhiteSpace(url))
|
|
||||||
{
|
|
||||||
if (url.Contains("bitwarden.com") || url.Contains("bitwarden.eu"))
|
|
||||||
{
|
|
||||||
return CoreHelpers.GetDomain(url);
|
|
||||||
}
|
|
||||||
return CoreHelpers.GetHostname(url);
|
|
||||||
}
|
|
||||||
return string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAccount { get; set; }
|
public bool IsAccount { get; set; }
|
||||||
|
|
|
@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
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.Data;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Models.Response;
|
using Bit.Core.Models.Response;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
@ -54,7 +54,7 @@ namespace Bit.Core.Services
|
||||||
public string IdentityBaseUrl { get; set; }
|
public string IdentityBaseUrl { get; set; }
|
||||||
public string EventsBaseUrl { get; set; }
|
public string EventsBaseUrl { get; set; }
|
||||||
|
|
||||||
public void SetUrls(EnvironmentUrls urls)
|
public void SetUrls(EnvironmentUrlData urls)
|
||||||
{
|
{
|
||||||
UrlsSet = true;
|
UrlsSet = true;
|
||||||
if (!string.IsNullOrWhiteSpace(urls.Base))
|
if (!string.IsNullOrWhiteSpace(urls.Base))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
@ -33,6 +34,7 @@ namespace Bit.Core.Services
|
||||||
public string IconsUrl { get; set; }
|
public string IconsUrl { get; set; }
|
||||||
public string NotificationsUrl { get; set; }
|
public string NotificationsUrl { get; set; }
|
||||||
public string EventsUrl { get; set; }
|
public string EventsUrl { get; set; }
|
||||||
|
public Region SelectedRegion { get; set; }
|
||||||
|
|
||||||
public string GetWebVaultUrl(bool returnNullIfDefault = false)
|
public string GetWebVaultUrl(bool returnNullIfDefault = false)
|
||||||
{
|
{
|
||||||
|
@ -54,38 +56,33 @@ namespace Bit.Core.Services
|
||||||
return GetWebVaultUrl(true) is string webVaultUrl ? $"{webVaultUrl}/#/send/" : DEFAULT_WEB_SEND_URL;
|
return GetWebVaultUrl(true) is string webVaultUrl ? $"{webVaultUrl}/#/send/" : DEFAULT_WEB_SEND_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetCurrentDomain()
|
||||||
|
{
|
||||||
|
return new EnvironmentUrlData
|
||||||
|
{
|
||||||
|
WebVault = WebVaultUrl,
|
||||||
|
Base = BaseUrl,
|
||||||
|
Api = ApiUrl,
|
||||||
|
Identity = IdentityUrl
|
||||||
|
}.GetDomainOrHostname();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SetUrlsFromStorageAsync()
|
public async Task SetUrlsFromStorageAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var region = await _stateService.GetActiveUserRegionAsync();
|
||||||
var urls = await _stateService.GetEnvironmentUrlsAsync();
|
var urls = await _stateService.GetEnvironmentUrlsAsync();
|
||||||
if (urls == null)
|
urls ??= await _stateService.GetPreAuthEnvironmentUrlsAsync();
|
||||||
{
|
|
||||||
urls = await _stateService.GetPreAuthEnvironmentUrlsAsync();
|
|
||||||
}
|
|
||||||
if (urls == null)
|
|
||||||
{
|
|
||||||
urls = new EnvironmentUrlData();
|
|
||||||
}
|
|
||||||
var envUrls = new EnvironmentUrls();
|
|
||||||
if (!string.IsNullOrWhiteSpace(urls.Base))
|
|
||||||
{
|
|
||||||
BaseUrl = envUrls.Base = urls.Base;
|
|
||||||
_apiService.SetUrls(envUrls);
|
|
||||||
|
|
||||||
|
if (urls == null || urls.IsEmpty)
|
||||||
|
{
|
||||||
|
await SetRegionAsync(Region.US);
|
||||||
_conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.EnvironmentUrlsInited);
|
_conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseUrl = urls.Base;
|
await SetRegionAsync(region.Value, urls);
|
||||||
WebVaultUrl = urls.WebVault;
|
|
||||||
ApiUrl = envUrls.Api = urls.Api;
|
|
||||||
IdentityUrl = envUrls.Identity = urls.Identity;
|
|
||||||
IconsUrl = urls.Icons;
|
|
||||||
NotificationsUrl = urls.Notifications;
|
|
||||||
EventsUrl = envUrls.Events = urls.Events;
|
|
||||||
_apiService.SetUrls(envUrls);
|
|
||||||
|
|
||||||
_conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.EnvironmentUrlsInited);
|
_conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.EnvironmentUrlsInited);
|
||||||
}
|
}
|
||||||
catch (System.Exception ex)
|
catch (System.Exception ex)
|
||||||
|
@ -96,15 +93,26 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EnvironmentUrlData> SetUrlsAsync(EnvironmentUrlData urls)
|
public async Task<EnvironmentUrlData> SetRegionAsync(Region region, EnvironmentUrlData selfHostedUrls = null)
|
||||||
{
|
{
|
||||||
urls.Base = FormatUrl(urls.Base);
|
EnvironmentUrlData urls;
|
||||||
urls.WebVault = FormatUrl(urls.WebVault);
|
|
||||||
urls.Api = FormatUrl(urls.Api);
|
if (region == Region.SelfHosted)
|
||||||
urls.Identity = FormatUrl(urls.Identity);
|
{
|
||||||
urls.Icons = FormatUrl(urls.Icons);
|
// If user saves a self-hosted region with empty fields, default to US
|
||||||
urls.Notifications = FormatUrl(urls.Notifications);
|
if (selfHostedUrls.IsEmpty)
|
||||||
urls.Events = FormatUrl(urls.Events);
|
{
|
||||||
|
return await SetRegionAsync(Region.US);
|
||||||
|
}
|
||||||
|
urls = selfHostedUrls.FormatUrls();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
urls = region.GetUrls();
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedRegion = region;
|
||||||
|
await _stateService.SetPreAuthRegionAsync(region);
|
||||||
await _stateService.SetPreAuthEnvironmentUrlsAsync(urls);
|
await _stateService.SetPreAuthEnvironmentUrlsAsync(urls);
|
||||||
BaseUrl = urls.Base;
|
BaseUrl = urls.Base;
|
||||||
WebVaultUrl = urls.WebVault;
|
WebVaultUrl = urls.WebVault;
|
||||||
|
@ -113,35 +121,8 @@ namespace Bit.Core.Services
|
||||||
IconsUrl = urls.Icons;
|
IconsUrl = urls.Icons;
|
||||||
NotificationsUrl = urls.Notifications;
|
NotificationsUrl = urls.Notifications;
|
||||||
EventsUrl = urls.Events;
|
EventsUrl = urls.Events;
|
||||||
|
_apiService.SetUrls(urls);
|
||||||
var envUrls = new EnvironmentUrls();
|
|
||||||
if (!string.IsNullOrWhiteSpace(BaseUrl))
|
|
||||||
{
|
|
||||||
envUrls.Base = BaseUrl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
envUrls.Api = ApiUrl;
|
|
||||||
envUrls.Identity = IdentityUrl;
|
|
||||||
envUrls.Events = EventsUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
_apiService.SetUrls(envUrls);
|
|
||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatUrl(string url)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(url))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
url = Regex.Replace(url, "\\/+$", string.Empty);
|
|
||||||
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
|
|
||||||
{
|
|
||||||
url = string.Concat("https://", url);
|
|
||||||
}
|
|
||||||
return url.Trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,13 @@ using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public class StateMigrationService : IStateMigrationService
|
public class StateMigrationService : IStateMigrationService
|
||||||
{
|
{
|
||||||
private const int StateVersion = 6;
|
private const int StateVersion = 7;
|
||||||
|
|
||||||
private readonly DeviceType _deviceType;
|
private readonly DeviceType _deviceType;
|
||||||
private readonly IStorageService _preferencesStorageService;
|
private readonly IStorageService _preferencesStorageService;
|
||||||
|
@ -86,6 +87,9 @@ namespace Bit.Core.Services
|
||||||
goto case 5;
|
goto case 5;
|
||||||
case 5:
|
case 5:
|
||||||
await MigrateFrom5To6Async();
|
await MigrateFrom5To6Async();
|
||||||
|
goto case 6;
|
||||||
|
case 6:
|
||||||
|
await MigrateFrom6To7Async();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -837,6 +841,42 @@ namespace Bit.Core.Services
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region v6 to v7 Migration
|
||||||
|
|
||||||
|
private class V7Keys
|
||||||
|
{
|
||||||
|
// global keys
|
||||||
|
internal const string StateKey = "state";
|
||||||
|
internal const string RegionEnvironmentKey = "regionEnvironment";
|
||||||
|
internal const string PreAuthEnvironmentUrlsKey = "preAuthEnvironmentUrls";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MigrateFrom6To7Async()
|
||||||
|
{
|
||||||
|
// account data
|
||||||
|
var state = await GetValueAsync<State>(Storage.Prefs, V7Keys.StateKey);
|
||||||
|
|
||||||
|
// Migrate environment data to use Regions
|
||||||
|
foreach (var account in state.Accounts.Where(a => a.Value?.Profile?.UserId != null && a.Value?.Settings != null))
|
||||||
|
{
|
||||||
|
var urls = account.Value.Settings.EnvironmentUrls ?? Region.US.GetUrls();
|
||||||
|
account.Value.Settings.Region = urls.Region;
|
||||||
|
account.Value.Settings.EnvironmentUrls = urls.Region.GetUrls() ?? urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SetValueAsync(Storage.Prefs, Constants.StateKey, state);
|
||||||
|
|
||||||
|
// Update pre auth urls and region
|
||||||
|
var preAuthUrls = await GetValueAsync<EnvironmentUrlData>(Storage.Prefs, V7Keys.PreAuthEnvironmentUrlsKey) ?? Region.US.GetUrls();
|
||||||
|
await SetValueAsync(Storage.Prefs, V7Keys.RegionEnvironmentKey, preAuthUrls.Region);
|
||||||
|
await SetValueAsync(Storage.Prefs, V7Keys.PreAuthEnvironmentUrlsKey, preAuthUrls.Region.GetUrls() ?? preAuthUrls);
|
||||||
|
|
||||||
|
|
||||||
|
// Update stored version
|
||||||
|
await SetLastStateVersionAsync(7);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
private async Task<int> GetLastStateVersionAsync()
|
private async Task<int> GetLastStateVersionAsync()
|
||||||
|
|
|
@ -1363,6 +1363,21 @@ namespace Bit.Core.Services
|
||||||
_storageMediatorService.Save(Constants.ConfigsKey, value);
|
_storageMediatorService.Save(Constants.ConfigsKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Region?> GetActiveUserRegionAsync()
|
||||||
|
{
|
||||||
|
return await GetActiveUserCustomDataAsync(a => a?.Settings?.Region);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Region?> GetPreAuthRegionAsync()
|
||||||
|
{
|
||||||
|
return await _storageMediatorService.GetAsync<Region?>(Constants.RegionEnvironment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetPreAuthRegionAsync(Region value)
|
||||||
|
{
|
||||||
|
await _storageMediatorService.SaveAsync(Constants.RegionEnvironment, value);
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
[Obsolete("Use IStorageMediatorService instead")]
|
[Obsolete("Use IStorageMediatorService instead")]
|
||||||
|
@ -1552,6 +1567,7 @@ namespace Bit.Core.Services
|
||||||
await CheckStateAsync();
|
await CheckStateAsync();
|
||||||
|
|
||||||
account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync();
|
account.Settings.EnvironmentUrls = await GetPreAuthEnvironmentUrlsAsync();
|
||||||
|
account.Settings.Region = await GetPreAuthRegionAsync();
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
var state = await GetStateFromStorageAsync() ?? new State();
|
var state = await GetStateFromStorageAsync() ?? new State();
|
||||||
|
|
49
src/Core/Utilities/RegionExtensions.cs
Normal file
49
src/Core/Utilities/RegionExtensions.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
|
namespace Bit.Core.Utilities
|
||||||
|
{
|
||||||
|
public static class RegionExtensions
|
||||||
|
{
|
||||||
|
public static EnvironmentUrlData GetUrls(this Region region)
|
||||||
|
{
|
||||||
|
switch (region)
|
||||||
|
{
|
||||||
|
case Region.US:
|
||||||
|
return EnvironmentUrlData.DefaultUS;
|
||||||
|
case Region.EU:
|
||||||
|
return EnvironmentUrlData.DefaultEU;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BaseUrl(this Region region)
|
||||||
|
{
|
||||||
|
switch (region)
|
||||||
|
{
|
||||||
|
case Region.US:
|
||||||
|
return EnvironmentUrlData.DefaultUS.Base;
|
||||||
|
case Region.EU:
|
||||||
|
return EnvironmentUrlData.DefaultEU.Base;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Domain(this Region region)
|
||||||
|
{
|
||||||
|
switch (region)
|
||||||
|
{
|
||||||
|
case Region.US:
|
||||||
|
return EnvironmentUrlData.DefaultUS.Domain;
|
||||||
|
case Region.EU:
|
||||||
|
return EnvironmentUrlData.DefaultEU.Domain;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue