mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-22 11:35:21 +01:00
[SSO] Auto enroll during set password (#1520)
* [SSO] Auto enroll during set password * Updated with requested changes
This commit is contained in:
parent
8866fc6322
commit
d3f00340fb
@ -32,6 +32,28 @@
|
|||||||
StyleClass="text-md"
|
StyleClass="text-md"
|
||||||
HorizontalTextAlignment="Start"></Label>
|
HorizontalTextAlignment="Start"></Label>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
<Grid IsVisible="{Binding ResetPasswordAutoEnroll}"
|
||||||
|
RowSpacing="0"
|
||||||
|
ColumnSpacing="0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Frame Padding="10"
|
||||||
|
Margin="0, 12, 0, 0"
|
||||||
|
HasShadow="False"
|
||||||
|
BackgroundColor="Transparent"
|
||||||
|
BorderColor="Accent">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n ResetPasswordAutoEnrollInviteWarning}"
|
||||||
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
|
HorizontalTextAlignment="Start" />
|
||||||
|
</Frame>
|
||||||
|
</Grid>
|
||||||
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
||||||
RowSpacing="0"
|
RowSpacing="0"
|
||||||
ColumnSpacing="0">
|
ColumnSpacing="0">
|
||||||
@ -44,7 +66,7 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Frame Padding="10"
|
<Frame Padding="10"
|
||||||
Margin="0"
|
Margin="0, 12, 0, 0"
|
||||||
HasShadow="False"
|
HasShadow="False"
|
||||||
BackgroundColor="Transparent"
|
BackgroundColor="Transparent"
|
||||||
BorderColor="Accent">
|
BorderColor="Accent">
|
||||||
|
@ -30,6 +30,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private bool _isPolicyInEffect;
|
private bool _isPolicyInEffect;
|
||||||
|
private bool _resetPasswordAutoEnroll;
|
||||||
private string _policySummary;
|
private string _policySummary;
|
||||||
private MasterPasswordPolicyOptions _policy;
|
private MasterPasswordPolicyOptions _policy;
|
||||||
|
|
||||||
@ -50,7 +51,6 @@ namespace Bit.App.Pages
|
|||||||
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
|
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowPassword
|
public bool ShowPassword
|
||||||
{
|
{
|
||||||
get => _showPassword;
|
get => _showPassword;
|
||||||
@ -63,6 +63,12 @@ namespace Bit.App.Pages
|
|||||||
get => _isPolicyInEffect;
|
get => _isPolicyInEffect;
|
||||||
set => SetProperty(ref _isPolicyInEffect, value);
|
set => SetProperty(ref _isPolicyInEffect, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ResetPasswordAutoEnroll
|
||||||
|
{
|
||||||
|
get => _resetPasswordAutoEnroll;
|
||||||
|
set => SetProperty(ref _resetPasswordAutoEnroll, value);
|
||||||
|
}
|
||||||
|
|
||||||
public string PolicySummary
|
public string PolicySummary
|
||||||
{
|
{
|
||||||
@ -86,10 +92,17 @@ namespace Bit.App.Pages
|
|||||||
public Action SetPasswordSuccessAction { get; set; }
|
public Action SetPasswordSuccessAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public string OrgIdentifier { get; set; }
|
public string OrgIdentifier { get; set; }
|
||||||
|
public string OrgId { get; set; }
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
await CheckPasswordPolicy();
|
await CheckPasswordPolicy();
|
||||||
|
|
||||||
|
var org = await _userService.GetOrganizationByIdentifierAsync(OrgIdentifier);
|
||||||
|
OrgId = org?.Id;
|
||||||
|
var policyList = await _policyService.GetAll(PolicyType.ResetPassword);
|
||||||
|
var policyResult = _policyService.GetResetPasswordPolicyOptions(policyList, OrgId);
|
||||||
|
ResetPasswordAutoEnroll = policyResult.Item2 && policyResult.Item1.AutoEnrollEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
@ -171,6 +184,7 @@ namespace Bit.App.Pages
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
|
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
|
||||||
|
// Set Password and relevant information
|
||||||
await _apiService.SetPasswordAsync(request);
|
await _apiService.SetPasswordAsync(request);
|
||||||
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
|
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
|
||||||
await _userService.GetEmailAsync(), kdf, kdfIterations);
|
await _userService.GetEmailAsync(), kdf, kdfIterations);
|
||||||
@ -178,6 +192,25 @@ namespace Bit.App.Pages
|
|||||||
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
|
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
|
||||||
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||||
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
|
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
|
||||||
|
|
||||||
|
if (ResetPasswordAutoEnroll)
|
||||||
|
{
|
||||||
|
// Grab Organization Keys
|
||||||
|
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
|
||||||
|
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
|
||||||
|
// Grab user's Encryption Key and encrypt with Org Public Key
|
||||||
|
var userEncKey = await _cryptoService.GetEncKeyAsync();
|
||||||
|
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey);
|
||||||
|
// Request
|
||||||
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
|
{
|
||||||
|
ResetPasswordKey = encryptedKey.EncryptedString
|
||||||
|
};
|
||||||
|
var userId = await _userService.GetUserIdAsync();
|
||||||
|
// Enroll user
|
||||||
|
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest);
|
||||||
|
}
|
||||||
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|
||||||
SetPasswordSuccessAction?.Invoke();
|
SetPasswordSuccessAction?.Invoke();
|
||||||
|
6
src/App/Resources/AppResources.Designer.cs
generated
6
src/App/Resources/AppResources.Designer.cs
generated
@ -3590,5 +3590,11 @@ namespace Bit.App.Resources {
|
|||||||
return ResourceManager.GetString("Fido2SomethingWentWrong", resourceCulture);
|
return ResourceManager.GetString("Fido2SomethingWentWrong", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ResetPasswordAutoEnrollInviteWarning {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ResetPasswordAutoEnrollInviteWarning", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2031,4 +2031,7 @@
|
|||||||
<data name="Fido2SomethingWentWrong" xml:space="preserve">
|
<data name="Fido2SomethingWentWrong" xml:space="preserve">
|
||||||
<value>Something Went Wrong. Try again.</value>
|
<value>Something Went Wrong. Try again.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ResetPasswordAutoEnrollInviteWarning" xml:space="preserve">
|
||||||
|
<value>This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
@ -58,6 +58,9 @@ namespace Bit.Core.Abstractions
|
|||||||
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
|
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
|
||||||
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
||||||
Task PostEventsCollectAsync(IEnumerable<EventRequest> request);
|
Task PostEventsCollectAsync(IEnumerable<EventRequest> request);
|
||||||
|
Task<OrganizationKeysResponse> GetOrganizationKeysAsync(string id);
|
||||||
|
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
|
||||||
|
OrganizationUserResetPasswordEnrollmentRequest request);
|
||||||
|
|
||||||
Task<SendResponse> GetSendAsync(string id);
|
Task<SendResponse> GetSendAsync(string id);
|
||||||
Task<SendResponse> PostSendAsync(SendRequest request);
|
Task<SendResponse> PostSendAsync(SendRequest request);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -15,5 +16,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<MasterPasswordPolicyOptions> GetMasterPasswordPolicyOptions(IEnumerable<Policy> policies = null);
|
Task<MasterPasswordPolicyOptions> GetMasterPasswordPolicyOptions(IEnumerable<Policy> policies = null);
|
||||||
Task<bool> EvaluateMasterPassword(int passwordStrength, string newPassword,
|
Task<bool> EvaluateMasterPassword(int passwordStrength, string newPassword,
|
||||||
MasterPasswordPolicyOptions enforcedPolicyOptions);
|
MasterPasswordPolicyOptions enforcedPolicyOptions);
|
||||||
|
Tuple<ResetPasswordPolicyOptions, bool> GetResetPasswordPolicyOptions(IEnumerable<Policy> policies,
|
||||||
|
string orgId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<KdfType?> GetKdfAsync();
|
Task<KdfType?> GetKdfAsync();
|
||||||
Task<int?> GetKdfIterationsAsync();
|
Task<int?> GetKdfIterationsAsync();
|
||||||
Task<Organization> GetOrganizationAsync(string id);
|
Task<Organization> GetOrganizationAsync(string id);
|
||||||
|
Task<Organization> GetOrganizationByIdentifierAsync(string identifier);
|
||||||
Task<string> GetSecurityStampAsync();
|
Task<string> GetSecurityStampAsync();
|
||||||
Task<bool> GetEmailVerifiedAsync();
|
Task<bool> GetEmailVerifiedAsync();
|
||||||
Task<string> GetUserIdAsync();
|
Task<string> GetUserIdAsync();
|
||||||
|
@ -10,5 +10,6 @@
|
|||||||
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
|
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
|
||||||
DisableSend = 6, // Disables the ability to create and edit Sends
|
DisableSend = 6, // Disables the ability to create and edit Sends
|
||||||
SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends
|
SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends
|
||||||
|
ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Enums;
|
using System.Data.Common;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Response;
|
using Bit.Core.Models.Response;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Data
|
namespace Bit.Core.Models.Data
|
||||||
@ -27,6 +28,7 @@ namespace Bit.Core.Models.Data
|
|||||||
MaxCollections = response.MaxCollections;
|
MaxCollections = response.MaxCollections;
|
||||||
MaxStorageGb = response.MaxStorageGb;
|
MaxStorageGb = response.MaxStorageGb;
|
||||||
Permissions = response.Permissions ?? new Permissions();
|
Permissions = response.Permissions ?? new Permissions();
|
||||||
|
Identifier = response.Identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
@ -47,5 +49,6 @@ namespace Bit.Core.Models.Data
|
|||||||
public short? MaxCollections { get; set; }
|
public short? MaxCollections { get; set; }
|
||||||
public short? MaxStorageGb { get; set; }
|
public short? MaxStorageGb { get; set; }
|
||||||
public Permissions Permissions { get; set; } = new Permissions();
|
public Permissions Permissions { get; set; } = new Permissions();
|
||||||
|
public string Identifier { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Enums;
|
using System.Data.Common;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Domain
|
namespace Bit.Core.Models.Domain
|
||||||
@ -27,6 +28,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
MaxCollections = obj.MaxCollections;
|
MaxCollections = obj.MaxCollections;
|
||||||
MaxStorageGb = obj.MaxStorageGb;
|
MaxStorageGb = obj.MaxStorageGb;
|
||||||
Permissions = obj.Permissions ?? new Permissions();
|
Permissions = obj.Permissions ?? new Permissions();
|
||||||
|
Identifier = obj.Identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
@ -47,6 +49,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
public short? MaxCollections { get; set; }
|
public short? MaxCollections { get; set; }
|
||||||
public short? MaxStorageGb { get; set; }
|
public short? MaxStorageGb { get; set; }
|
||||||
public Permissions Permissions { get; set; } = new Permissions();
|
public Permissions Permissions { get; set; } = new Permissions();
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
|
||||||
public bool CanAccess
|
public bool CanAccess
|
||||||
{
|
{
|
||||||
|
7
src/Core/Models/Domain/ResetPasswordPolicyOptions.cs
Normal file
7
src/Core/Models/Domain/ResetPasswordPolicyOptions.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Bit.Core.Models.Domain
|
||||||
|
{
|
||||||
|
public class ResetPasswordPolicyOptions
|
||||||
|
{
|
||||||
|
public bool AutoEnrollEnabled { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace Bit.Core.Models.Request
|
||||||
|
{
|
||||||
|
public class OrganizationUserResetPasswordEnrollmentRequest
|
||||||
|
{
|
||||||
|
public string ResetPasswordKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/Core/Models/Response/OrganizationKeysResponse.cs
Normal file
8
src/Core/Models/Response/OrganizationKeysResponse.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Bit.Core.Models.Response
|
||||||
|
{
|
||||||
|
public class OrganizationKeysResponse
|
||||||
|
{
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -24,5 +24,6 @@ namespace Bit.Core.Models.Response
|
|||||||
public OrganizationUserType Type { get; set; }
|
public OrganizationUserType Type { get; set; }
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public Permissions Permissions { get; set; } = new Permissions();
|
public Permissions Permissions { get; set; } = new Permissions();
|
||||||
|
public string Identifier { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,7 @@ namespace Bit.Core.Services
|
|||||||
return SendAsync<PasswordVerificationRequest, object>(HttpMethod.Post, "/accounts/verify-password", request,
|
return SendAsync<PasswordVerificationRequest, object>(HttpMethod.Post, "/accounts/verify-password", request,
|
||||||
true, false);
|
true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Folder APIs
|
#region Folder APIs
|
||||||
@ -402,6 +402,26 @@ namespace Bit.Core.Services
|
|||||||
string.Concat("/hibp/breach?username=", username), null, true, true);
|
string.Concat("/hibp/breach?username=", username), null, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Organizations APIs
|
||||||
|
|
||||||
|
public Task<OrganizationKeysResponse> GetOrganizationKeysAsync(string id)
|
||||||
|
{
|
||||||
|
return SendAsync<object, OrganizationKeysResponse>(HttpMethod.Get, $"/organizations/{id}/keys", null, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Organization User APIs
|
||||||
|
|
||||||
|
public Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
|
||||||
|
OrganizationUserResetPasswordEnrollmentRequest request)
|
||||||
|
{
|
||||||
|
return SendAsync<OrganizationUserResetPasswordEnrollmentRequest, object>(HttpMethod.Put,
|
||||||
|
$"/organizations/{orgId}/users/{userId}/reset-password-enrollment", request, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -180,6 +181,23 @@ namespace Bit.Core.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Tuple<ResetPasswordPolicyOptions, bool> GetResetPasswordPolicyOptions(IEnumerable<Policy> policies,
|
||||||
|
string orgId)
|
||||||
|
{
|
||||||
|
var resetPasswordPolicyOptions = new ResetPasswordPolicyOptions();
|
||||||
|
|
||||||
|
if (policies == null || orgId == null)
|
||||||
|
{
|
||||||
|
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var policy = policies.FirstOrDefault(p =>
|
||||||
|
p.OrganizationId == orgId && p.Type == PolicyType.ResetPassword && p.Enabled);
|
||||||
|
resetPasswordPolicyOptions.AutoEnrollEnabled = GetPolicyBool(policy, "autoEnrollEnabled") ?? false;
|
||||||
|
|
||||||
|
return new Tuple<ResetPasswordPolicyOptions, bool>(resetPasswordPolicyOptions, policy != null);
|
||||||
|
}
|
||||||
|
|
||||||
private int? GetPolicyInt(Policy policy, string key)
|
private int? GetPolicyInt(Policy policy, string key)
|
||||||
{
|
{
|
||||||
if (policy.Data.ContainsKey(key))
|
if (policy.Data.ContainsKey(key))
|
||||||
|
@ -167,6 +167,19 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
return new Organization(organizations[id]);
|
return new Organization(organizations[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Organization> GetOrganizationByIdentifierAsync(string identifier)
|
||||||
|
{
|
||||||
|
var userId = await GetUserIdAsync();
|
||||||
|
var organizations = await GetAllOrganizationAsync();
|
||||||
|
|
||||||
|
if (organizations == null || organizations.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return organizations.FirstOrDefault(o => o.Identifier == identifier);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<Organization>> GetAllOrganizationAsync()
|
public async Task<List<Organization>> GetAllOrganizationAsync()
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user