mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 02:18:27 +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
|
||||
{
|
||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||
await _stateService.SetPreLoginEmailAsync(Email);
|
||||
await AccountSwitchingOverlayViewModel.RefreshAccountViewsAsync();
|
||||
if (string.IsNullOrWhiteSpace(Email))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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>
|
||||
/// Looks up a localized string similar to Organization identifier.
|
||||
/// </summary>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue