[SG-744] Add claimed domain logic to mobile (#2333)

This commit is contained in:
André Bispo 2023-02-20 14:49:20 +00:00 committed by GitHub
parent b26b9ea41b
commit 3f72d35145
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 140 additions and 7 deletions

View file

@ -143,6 +143,7 @@ namespace Bit.App.Pages
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _stateService.SetPreLoginEmailAsync(Email);
await AccountSwitchingOverlayViewModel.RefreshAccountViewsAsync();
if (string.IsNullOrWhiteSpace(Email))
{

View file

@ -1,4 +1,5 @@
using System;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
@ -27,6 +28,7 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private readonly ILogger _logger;
private readonly IOrganizationService _organizationService;
private string _orgIdentifier;
@ -42,6 +44,7 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
PageTitle = AppResources.Bitwarden;
@ -63,9 +66,25 @@ namespace Bit.App.Pages
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);
}
}
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;
}
}
}

View file

@ -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>
/// Looks up a localized string similar to Organization identifier.
/// </summary>

View file

@ -2577,4 +2577,7 @@ Do you want to switch to this account?</value>
<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>
</data>
<data name="OrganizationSsoIdentifierRequired" xml:space="preserve">
<value>Organization SSO identifier required.</value>
</data>
</root>

View file

@ -90,5 +90,6 @@ namespace Bit.Core.Abstractions
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
}
}

View file

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
namespace Bit.Core.Abstractions
{
@ -12,5 +13,6 @@ namespace Bit.Core.Abstractions
Task<List<Organization>> GetAllAsync(string userId = null);
Task ReplaceAsync(Dictionary<string, OrganizationData> organizations);
Task ClearAllAsync(string userId);
Task<OrganizationDomainSsoDetailsResponse> GetClaimedOrganizationDomainAsync(string userEmail);
}
}

View file

@ -163,7 +163,7 @@ namespace Bit.Core.Abstractions
Task<bool> GetLastUserShouldConnectToWatchAsync();
Task SetAvatarColorAsync(string value, string userId = null);
Task<string> GetAvatarColorAsync(string userId = null);
Task<string> GetPreLoginEmailAsync();
Task SetPreLoginEmailAsync(string value);
}
}

View file

@ -40,6 +40,7 @@
public const string NotificationData = "notificationData";
public const string NotificationDataType = "Type";
public const string PasswordlessLoginRequestKey = "passwordlessLoginRequest";
public const string PreLoginEmailKey = "preLoginEmailKey";
/// <summary>
/// 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

View file

@ -0,0 +1,9 @@
using System;
namespace Bit.Core.Models.Request
{
public class OrganizationDomainSsoDetailsRequest
{
public string Email { get; set; }
}
}

View file

@ -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; }
}
}

View file

@ -457,6 +457,11 @@ namespace Bit.Core.Services
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
#region Organization User APIs

View file

@ -1,19 +1,27 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
public class OrganizationService : IOrganizationService
{
private readonly IStateService _stateService;
private readonly IApiService _apiService;
public OrganizationService(IStateService stateService)
public OrganizationService(IStateService stateService, IApiService apiService)
{
_stateService = stateService;
_apiService = apiService;
}
public async Task<Organization> GetAsync(string id)
@ -51,5 +59,23 @@ namespace Bit.Core.Services
{
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;
}
}
}
}

View file

@ -1208,6 +1208,18 @@ namespace Bit.Core.Services
))?.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
private async Task<T> GetValueAsync<T>(string key, StorageOptions options)

View file

@ -40,7 +40,7 @@ namespace Bit.Core.Utilities
return Task.CompletedTask;
}, customUserAgent);
var appIdService = new AppIdService(storageService);
var organizationService = new OrganizationService(stateService);
var organizationService = new OrganizationService(stateService, apiService);
var settingsService = new SettingsService(stateService);
var fileUploadService = new FileUploadService(apiService);
var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService,