1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-24 04:04:34 +00:00

[PM-1208] Add device related api endpoint. Add AccoundDecryptOptions model and property to user Account.

This commit is contained in:
André Bispo
2023-06-28 22:37:08 +01:00
parent 84a82f0876
commit 87866304a6
14 changed files with 148 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using Bit.App.Models;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -9,7 +9,7 @@ namespace Bit.App.Pages
{
private readonly LoginApproveDeviceViewModel _vm;
public LoginApproveDevicePage()
public LoginApproveDevicePage(AppOptions appOptions = null)
{
InitializeComponent();
_vm = BindingContext as LoginApproveDeviceViewModel;

View File

@@ -1,7 +1,13 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
namespace Bit.App.Pages
@@ -12,6 +18,8 @@ namespace Bit.App.Pages
private bool _approveWithMyOtherDeviceEnabled;
private bool _requestAdminApprovalEnabled;
private bool _approveWithMasterPasswordEnabled;
private readonly IStateService _stateService;
private readonly IApiService _apiService;
public ICommand ApproveWithMyOtherDeviceCommand { get; }
public ICommand RequestAdminApprovalCommand { get; }
@@ -19,7 +27,11 @@ namespace Bit.App.Pages
public LoginApproveDeviceViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>();
_apiService = ServiceContainer.Resolve<IApiService>();
PageTitle = AppResources.LoggedIn;
ApproveWithMyOtherDeviceCommand = new AsyncCommand(InitAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
@@ -59,9 +71,25 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
ApproveWithMyOtherDeviceEnabled = true;
RequestAdminApprovalEnabled = true;
ApproveWithMasterPasswordEnabled = true;
try
{
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
RequestAdminApprovalEnabled = decryptOptions.TrustedDeviceOption.HasAdminApproval;
ApproveWithMasterPasswordEnabled = decryptOptions.HasMasterPassword;
}
catch (Exception ex)
{
HandleException(ex);
}
try
{
ApproveWithMyOtherDeviceEnabled = await _apiService.GetDevicesExistenceByTypes(DeviceTypeExtensions.GetDesktopAndMobileTypes().ToArray());
}
catch (Exception ex)
{
HandleException(ex);
}
}
}
}

View File

@@ -110,6 +110,11 @@ namespace Bit.App.Pages
{
RestoreAppOptionsFromCopy();
await AppHelpers.ClearPreviousPage();
// Just for testing the screen
Application.Current.MainPage = new NavigationPage(new LoginApproveDevicePage(_appOptions));
return;
if (await _vaultTimeoutService.IsLockedAsync())
{
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));

View File

@@ -92,5 +92,7 @@ namespace Bit.Core.Abstractions
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
Task<DeviceResponse> PutUpdateTrustedDeviceKeys(UpdateTrustedDeviceKeysRequest request);
}
}

View File

@@ -172,6 +172,7 @@ namespace Bit.Core.Abstractions
Task<string> GetAvatarColorAsync(string userId = null);
Task<string> GetPreLoginEmailAsync();
Task SetPreLoginEmailAsync(string value);
Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null);
string GetLocale();
void SetLocale(string locale);
}

View File

@@ -1,4 +1,7 @@
namespace Bit.Core.Enums
using System.Collections.Generic;
using System.Linq;
namespace Bit.Core.Enums
{
public enum DeviceType : byte
{
@@ -24,4 +27,24 @@
VivaldiExtension = 19,
SafariExtension = 20
}
public static class DeviceTypeExtensions
{
public static List<DeviceType> GetMobileTypes() => new List<DeviceType>
{
DeviceType.Android,
DeviceType.AndroidAmazon,
DeviceType.iOS
};
public static List<DeviceType> GetDesktopTypes() => new List<DeviceType>
{
DeviceType.WindowsDesktop,
DeviceType.MacOsDesktop,
DeviceType.LinuxDesktop,
DeviceType.UWP,
};
public static List<DeviceType> GetDesktopAndMobileTypes() => GetMobileTypes().Concat(GetDesktopTypes()).ToList();
}
}

View File

@@ -53,6 +53,7 @@ namespace Bit.Core.Models.Domain
HasPremiumPersonally = copy.HasPremiumPersonally;
AvatarColor = copy.AvatarColor;
ForcePasswordResetReason = copy.ForcePasswordResetReason;
UserDecryptionOptions = copy.UserDecryptionOptions;
}
public string UserId;
@@ -68,6 +69,7 @@ namespace Bit.Core.Models.Domain
public bool? EmailVerified;
public bool? HasPremiumPersonally;
public ForcePasswordResetReason? ForcePasswordResetReason;
public AccountDecryptionOptions UserDecryptionOptions;
}
public class AccountTokens

View File

@@ -0,0 +1,21 @@
using System;
namespace Bit.Core.Models.Domain
{
public class AccountDecryptionOptions
{
public bool HasMasterPassword { get; set; }
public TrustedDeviceOption TrustedDeviceOption { get; set; }
public KeyConnectorOption KeyConnectorOption { get; set; }
}
public class TrustedDeviceOption
{
public bool HasAdminApproval { get; set; }
}
public class KeyConnectorOption
{
public bool KeyConnectorUrl { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Bit.Core.Models.Request
{
public class UpdateTrustedDeviceKeysRequest
{
public string DeviceIdentifier { get; set; }
public string DevicePublicKeyEncryptedUserKey { get; set; }
public string UserKeyEncryptedDevicePublicKey { get; set; }
public string DeviceKeyEncryptedDevicePrivateKey { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
using Bit.Core.Enums;
namespace Bit.Core.Models.Response
{
public class DeviceResponse
{
public string Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string Identifier { get; set; }
public DeviceType Type { get; set; }
public string CreationDate { get; set; }
public string RevisionDate { get; set; }
}
}

View File

@@ -27,6 +27,7 @@ namespace Bit.Core.Models.Response
public bool ForcePasswordReset { get; set; }
public string KeyConnectorUrl { get; set; }
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
public AccountDecryptionOptions UserDecryptionOptions { get; set; }
[JsonIgnore]
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
}

View File

@@ -395,12 +395,34 @@ namespace Bit.Core.Services
#region Device APIs
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
{
return SendAsync<object, bool>(HttpMethod.Get, "/devices/knowndevice", null, false, true, (message) =>
{
message.Headers.Add("X-Device-Identifier", deviceIdentifier);
message.Headers.Add("X-Request-Email", CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes(email)));
});
}
public Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request)
{
return SendAsync<DeviceTokenRequest, object>(
HttpMethod.Put, $"/devices/identifier/{identifier}/token", request, true, false);
}
public Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes)
{
return SendAsync<DeviceType[], bool>(
HttpMethod.Post, "/devices/exist-by-types", deviceTypes, true, true);
}
public Task<DeviceResponse> PutUpdateTrustedDeviceKeys(UpdateTrustedDeviceKeysRequest request)
{
return SendAsync<UpdateTrustedDeviceKeysRequest, DeviceResponse>(
HttpMethod.Put, $"/devices/${request.DeviceIdentifier}/keys", request, true, true);
}
#endregion
#region Event APIs
@@ -574,15 +596,6 @@ namespace Bit.Core.Services
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Put, $"/auth-requests/{id}", request, true, true);
}
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
{
return SendAsync<object, bool>(HttpMethod.Get, "/devices/knowndevice", null, false, true, (message) =>
{
message.Headers.Add("X-Device-Identifier", deviceIdentifier);
message.Headers.Add("X-Request-Email", CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes(email)));
});
}
#endregion
#region Helpers

View File

@@ -459,6 +459,7 @@ namespace Bit.Core.Services
ForcePasswordResetReason = result.ForcePasswordReset
? ForcePasswordResetReason.AdminForcePasswordReset
: (ForcePasswordResetReason?)null,
UserDecryptionOptions = tokenResponse.UserDecryptionOptions,
},
new Account.AccountTokens()
{

View File

@@ -1280,6 +1280,13 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.PreLoginEmailKey, value, options);
}
public async Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
))?.Profile?.UserDecryptionOptions;
}
// Helpers
[Obsolete("Use IStorageMediatorService instead")]