mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
[SG-744] Add claimed domain logic to mobile (#2333)
This commit is contained in:
parent
b26b9ea41b
commit
3f72d35145
14 changed files with 140 additions and 7 deletions
|
@ -143,6 +143,7 @@ namespace Bit.App.Pages
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
await _stateService.SetPreLoginEmailAsync(Email);
|
||||||
await AccountSwitchingOverlayViewModel.RefreshAccountViewsAsync();
|
await AccountSwitchingOverlayViewModel.RefreshAccountViewsAsync();
|
||||||
if (string.IsNullOrWhiteSpace(Email))
|
if (string.IsNullOrWhiteSpace(Email))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
@ -27,6 +28,7 @@ namespace Bit.App.Pages
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
|
|
||||||
private string _orgIdentifier;
|
private string _orgIdentifier;
|
||||||
|
|
||||||
|
@ -42,6 +44,7 @@ namespace Bit.App.Pages
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
|
||||||
|
|
||||||
|
|
||||||
PageTitle = AppResources.Bitwarden;
|
PageTitle = AppResources.Bitwarden;
|
||||||
|
@ -63,9 +66,25 @@ namespace Bit.App.Pages
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(OrgIdentifier))
|
try
|
||||||
{
|
{
|
||||||
OrgIdentifier = await _stateService.GetRememberedOrgIdentifierAsync();
|
if (await TryClaimedDomainLogin())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(OrgIdentifier))
|
||||||
|
{
|
||||||
|
OrgIdentifier = await _stateService.GetRememberedOrgIdentifierAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,5 +226,37 @@ namespace Bit.App.Pages
|
||||||
AppResources.AnErrorHasOccurred);
|
AppResources.AnErrorHasOccurred);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> TryClaimedDomainLogin()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
var userEmail = await _stateService.GetPreLoginEmailAsync();
|
||||||
|
var claimedDomainOrgDetails = await _organizationService.GetClaimedOrganizationDomainAsync(userEmail);
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|
||||||
|
if (claimedDomainOrgDetails == null || !claimedDomainOrgDetails.SsoAvailable)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(claimedDomainOrgDetails.OrganizationIdentifier))
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.OrganizationSsoIdentifierRequired, AppResources.AnErrorHasOccurred);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrgIdentifier = claimedDomainOrgDetails.OrganizationIdentifier;
|
||||||
|
await LogInAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
HandleException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
src/App/Resources/AppResources.Designer.cs
generated
9
src/App/Resources/AppResources.Designer.cs
generated
|
@ -4564,6 +4564,15 @@ namespace Bit.App.Resources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Organization SSO identifier required..
|
||||||
|
/// </summary>
|
||||||
|
public static string OrganizationSsoIdentifierRequired {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OrganizationSsoIdentifierRequired", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Organization identifier.
|
/// Looks up a localized string similar to Organization identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -2577,4 +2577,7 @@ Do you want to switch to this account?</value>
|
||||||
<data name="WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription" xml:space="preserve">
|
<data name="WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription" xml:space="preserve">
|
||||||
<value>Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?</value>
|
<value>Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="OrganizationSsoIdentifierRequired" xml:space="preserve">
|
||||||
|
<value>Organization SSO identifier required.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
|
@ -90,5 +90,6 @@ namespace Bit.Core.Abstractions
|
||||||
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
||||||
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
|
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
|
||||||
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
||||||
|
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
|
@ -12,5 +13,6 @@ namespace Bit.Core.Abstractions
|
||||||
Task<List<Organization>> GetAllAsync(string userId = null);
|
Task<List<Organization>> GetAllAsync(string userId = null);
|
||||||
Task ReplaceAsync(Dictionary<string, OrganizationData> organizations);
|
Task ReplaceAsync(Dictionary<string, OrganizationData> organizations);
|
||||||
Task ClearAllAsync(string userId);
|
Task ClearAllAsync(string userId);
|
||||||
|
Task<OrganizationDomainSsoDetailsResponse> GetClaimedOrganizationDomainAsync(string userEmail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,7 @@ namespace Bit.Core.Abstractions
|
||||||
Task<bool> GetLastUserShouldConnectToWatchAsync();
|
Task<bool> GetLastUserShouldConnectToWatchAsync();
|
||||||
Task SetAvatarColorAsync(string value, string userId = null);
|
Task SetAvatarColorAsync(string value, string userId = null);
|
||||||
Task<string> GetAvatarColorAsync(string userId = null);
|
Task<string> GetAvatarColorAsync(string userId = null);
|
||||||
|
Task<string> GetPreLoginEmailAsync();
|
||||||
|
Task SetPreLoginEmailAsync(string value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
public const string NotificationData = "notificationData";
|
public const string NotificationData = "notificationData";
|
||||||
public const string NotificationDataType = "Type";
|
public const string NotificationDataType = "Type";
|
||||||
public const string PasswordlessLoginRequestKey = "passwordlessLoginRequest";
|
public const string PasswordlessLoginRequestKey = "passwordlessLoginRequest";
|
||||||
|
public const string PreLoginEmailKey = "preLoginEmailKey";
|
||||||
/// <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
|
||||||
/// which is used to handle Apple Watch state logic
|
/// which is used to handle Apple Watch state logic
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
namespace Bit.Core.Models.Request
|
||||||
|
{
|
||||||
|
public class OrganizationDomainSsoDetailsRequest
|
||||||
|
{
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
namespace Bit.Core.Models.Response
|
||||||
|
{
|
||||||
|
public class OrganizationDomainSsoDetailsResponse
|
||||||
|
{
|
||||||
|
public bool SsoAvailable { get; set; }
|
||||||
|
public string DomainName { get; set; }
|
||||||
|
public string OrganizationIdentifier { get; set; }
|
||||||
|
public bool SsoRequired { get; set; }
|
||||||
|
public DateTime? VerifiedDate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -457,6 +457,11 @@ namespace Bit.Core.Services
|
||||||
return SendAsync<object, object>(HttpMethod.Post, $"/organizations/{id}/leave", null, true, false);
|
return SendAsync<object, object>(HttpMethod.Post, $"/organizations/{id}/leave", null, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string userEmail)
|
||||||
|
{
|
||||||
|
return SendAsync<OrganizationDomainSsoDetailsRequest, OrganizationDomainSsoDetailsResponse>(HttpMethod.Post, $"/organizations/domain/sso/details", new OrganizationDomainSsoDetailsRequest { Email = userEmail }, false, true);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Organization User APIs
|
#region Organization User APIs
|
||||||
|
|
|
@ -1,19 +1,27 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public class OrganizationService : IOrganizationService
|
public class OrganizationService : IOrganizationService
|
||||||
{
|
{
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
|
||||||
public OrganizationService(IStateService stateService)
|
public OrganizationService(IStateService stateService, IApiService apiService)
|
||||||
{
|
{
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
|
_apiService = apiService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Organization> GetAsync(string id)
|
public async Task<Organization> GetAsync(string id)
|
||||||
|
@ -51,5 +59,23 @@ namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
await _stateService.SetOrganizationsAsync(null, userId);
|
await _stateService.SetOrganizationsAsync(null, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<OrganizationDomainSsoDetailsResponse> GetClaimedOrganizationDomainAsync(string userEmail)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(userEmail))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _apiService.GetOrgDomainSsoDetailsAsync(userEmail);
|
||||||
|
}
|
||||||
|
catch (ApiException ex) when (ex.Error?.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
// this is a valid case so there is no need to show an error
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1208,6 +1208,18 @@ namespace Bit.Core.Services
|
||||||
))?.Profile?.AvatarColor;
|
))?.Profile?.AvatarColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetPreLoginEmailAsync()
|
||||||
|
{
|
||||||
|
var options = await GetDefaultStorageOptionsAsync();
|
||||||
|
return await GetValueAsync<string>(Constants.PreLoginEmailKey, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetPreLoginEmailAsync(string value)
|
||||||
|
{
|
||||||
|
var options = await GetDefaultStorageOptionsAsync();
|
||||||
|
await SetValueAsync(Constants.PreLoginEmailKey, value, options);
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
private async Task<T> GetValueAsync<T>(string key, StorageOptions options)
|
private async Task<T> GetValueAsync<T>(string key, StorageOptions options)
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace Bit.Core.Utilities
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}, customUserAgent);
|
}, customUserAgent);
|
||||||
var appIdService = new AppIdService(storageService);
|
var appIdService = new AppIdService(storageService);
|
||||||
var organizationService = new OrganizationService(stateService);
|
var organizationService = new OrganizationService(stateService, apiService);
|
||||||
var settingsService = new SettingsService(stateService);
|
var settingsService = new SettingsService(stateService);
|
||||||
var fileUploadService = new FileUploadService(apiService);
|
var fileUploadService = new FileUploadService(apiService);
|
||||||
var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService,
|
var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService,
|
||||||
|
|
Loading…
Reference in a new issue