diff --git a/src/App/Pages/Accounts/SetPasswordPage.xaml b/src/App/Pages/Accounts/SetPasswordPage.xaml
index ce0b2286f..2393ce0ab 100644
--- a/src/App/Pages/Accounts/SetPasswordPage.xaml
+++ b/src/App/Pages/Accounts/SetPasswordPage.xaml
@@ -32,6 +32,28 @@
StyleClass="text-md"
HorizontalTextAlignment="Start">
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -44,7 +66,7 @@
diff --git a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs
index 78f850017..60f53a2c6 100644
--- a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs
+++ b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs
@@ -30,6 +30,7 @@ namespace Bit.App.Pages
private bool _showPassword;
private bool _isPolicyInEffect;
+ private bool _resetPasswordAutoEnroll;
private string _policySummary;
private MasterPasswordPolicyOptions _policy;
@@ -50,7 +51,6 @@ namespace Bit.App.Pages
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
SubmitCommand = new Command(async () => await SubmitAsync());
}
-
public bool ShowPassword
{
get => _showPassword;
@@ -63,6 +63,12 @@ namespace Bit.App.Pages
get => _isPolicyInEffect;
set => SetProperty(ref _isPolicyInEffect, value);
}
+
+ public bool ResetPasswordAutoEnroll
+ {
+ get => _resetPasswordAutoEnroll;
+ set => SetProperty(ref _resetPasswordAutoEnroll, value);
+ }
public string PolicySummary
{
@@ -86,10 +92,17 @@ namespace Bit.App.Pages
public Action SetPasswordSuccessAction { get; set; }
public Action CloseAction { get; set; }
public string OrgIdentifier { get; set; }
+ public string OrgId { get; set; }
public async Task InitAsync()
{
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()
@@ -171,6 +184,7 @@ namespace Bit.App.Pages
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
+ // Set Password and relevant information
await _apiService.SetPasswordAsync(request);
await _userService.SetInformationAsync(await _userService.GetUserIdAsync(),
await _userService.GetEmailAsync(), kdf, kdfIterations);
@@ -178,6 +192,25 @@ namespace Bit.App.Pages
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.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();
SetPasswordSuccessAction?.Invoke();
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index b7dee1148..576767f43 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -3590,5 +3590,11 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("Fido2SomethingWentWrong", resourceCulture);
}
}
+
+ public static string ResetPasswordAutoEnrollInviteWarning {
+ get {
+ return ResourceManager.GetString("ResetPasswordAutoEnrollInviteWarning", resourceCulture);
+ }
+ }
}
}
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index 5301e085d..180109bd3 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -2031,4 +2031,7 @@
Something Went Wrong. Try again.
+
+ This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password.
+
diff --git a/src/Core/Abstractions/IApiService.cs b/src/Core/Abstractions/IApiService.cs
index 01394172a..8a29eb535 100644
--- a/src/Core/Abstractions/IApiService.cs
+++ b/src/Core/Abstractions/IApiService.cs
@@ -58,6 +58,9 @@ namespace Bit.Core.Abstractions
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
Task PostEventsCollectAsync(IEnumerable request);
+ Task GetOrganizationKeysAsync(string id);
+ Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
+ OrganizationUserResetPasswordEnrollmentRequest request);
Task GetSendAsync(string id);
Task PostSendAsync(SendRequest request);
diff --git a/src/Core/Abstractions/IPolicyService.cs b/src/Core/Abstractions/IPolicyService.cs
index cab335f0f..ef6b7e44f 100644
--- a/src/Core/Abstractions/IPolicyService.cs
+++ b/src/Core/Abstractions/IPolicyService.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Enums;
@@ -15,5 +16,7 @@ namespace Bit.Core.Abstractions
Task GetMasterPasswordPolicyOptions(IEnumerable policies = null);
Task EvaluateMasterPassword(int passwordStrength, string newPassword,
MasterPasswordPolicyOptions enforcedPolicyOptions);
+ Tuple GetResetPasswordPolicyOptions(IEnumerable policies,
+ string orgId);
}
}
diff --git a/src/Core/Abstractions/IUserService.cs b/src/Core/Abstractions/IUserService.cs
index fd640569d..dbdd17e83 100644
--- a/src/Core/Abstractions/IUserService.cs
+++ b/src/Core/Abstractions/IUserService.cs
@@ -16,6 +16,7 @@ namespace Bit.Core.Abstractions
Task GetKdfAsync();
Task GetKdfIterationsAsync();
Task GetOrganizationAsync(string id);
+ Task GetOrganizationByIdentifierAsync(string identifier);
Task GetSecurityStampAsync();
Task GetEmailVerifiedAsync();
Task GetUserIdAsync();
diff --git a/src/Core/Enums/PolicyType.cs b/src/Core/Enums/PolicyType.cs
index 8dd175da9..ab3b20bfd 100644
--- a/src/Core/Enums/PolicyType.cs
+++ b/src/Core/Enums/PolicyType.cs
@@ -10,5 +10,6 @@
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
DisableSend = 6, // Disables the ability to create and edit 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
}
}
diff --git a/src/Core/Models/Data/OrganizationData.cs b/src/Core/Models/Data/OrganizationData.cs
index 7fe8f94be..562a589a3 100644
--- a/src/Core/Models/Data/OrganizationData.cs
+++ b/src/Core/Models/Data/OrganizationData.cs
@@ -1,4 +1,5 @@
-using Bit.Core.Enums;
+using System.Data.Common;
+using Bit.Core.Enums;
using Bit.Core.Models.Response;
namespace Bit.Core.Models.Data
@@ -27,6 +28,7 @@ namespace Bit.Core.Models.Data
MaxCollections = response.MaxCollections;
MaxStorageGb = response.MaxStorageGb;
Permissions = response.Permissions ?? new Permissions();
+ Identifier = response.Identifier;
}
public string Id { get; set; }
@@ -47,5 +49,6 @@ namespace Bit.Core.Models.Data
public short? MaxCollections { get; set; }
public short? MaxStorageGb { get; set; }
public Permissions Permissions { get; set; } = new Permissions();
+ public string Identifier { get; set; }
}
}
diff --git a/src/Core/Models/Domain/Organization.cs b/src/Core/Models/Domain/Organization.cs
index 7e2046212..fb8ee271a 100644
--- a/src/Core/Models/Domain/Organization.cs
+++ b/src/Core/Models/Domain/Organization.cs
@@ -1,4 +1,5 @@
-using Bit.Core.Enums;
+using System.Data.Common;
+using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Core.Models.Domain
@@ -27,6 +28,7 @@ namespace Bit.Core.Models.Domain
MaxCollections = obj.MaxCollections;
MaxStorageGb = obj.MaxStorageGb;
Permissions = obj.Permissions ?? new Permissions();
+ Identifier = obj.Identifier;
}
public string Id { get; set; }
@@ -47,6 +49,7 @@ namespace Bit.Core.Models.Domain
public short? MaxCollections { get; set; }
public short? MaxStorageGb { get; set; }
public Permissions Permissions { get; set; } = new Permissions();
+ public string Identifier { get; set; }
public bool CanAccess
{
diff --git a/src/Core/Models/Domain/ResetPasswordPolicyOptions.cs b/src/Core/Models/Domain/ResetPasswordPolicyOptions.cs
new file mode 100644
index 000000000..1653aa774
--- /dev/null
+++ b/src/Core/Models/Domain/ResetPasswordPolicyOptions.cs
@@ -0,0 +1,7 @@
+namespace Bit.Core.Models.Domain
+{
+ public class ResetPasswordPolicyOptions
+ {
+ public bool AutoEnrollEnabled { get; set; }
+ }
+}
diff --git a/src/Core/Models/Request/OrganizationUserResetPasswordEnrollmentRequest.cs b/src/Core/Models/Request/OrganizationUserResetPasswordEnrollmentRequest.cs
new file mode 100644
index 000000000..b751a9a4f
--- /dev/null
+++ b/src/Core/Models/Request/OrganizationUserResetPasswordEnrollmentRequest.cs
@@ -0,0 +1,7 @@
+namespace Bit.Core.Models.Request
+{
+ public class OrganizationUserResetPasswordEnrollmentRequest
+ {
+ public string ResetPasswordKey { get; set; }
+ }
+}
diff --git a/src/Core/Models/Response/OrganizationKeysResponse.cs b/src/Core/Models/Response/OrganizationKeysResponse.cs
new file mode 100644
index 000000000..28d350f7b
--- /dev/null
+++ b/src/Core/Models/Response/OrganizationKeysResponse.cs
@@ -0,0 +1,8 @@
+namespace Bit.Core.Models.Response
+{
+ public class OrganizationKeysResponse
+ {
+ public string PrivateKey { get; set; }
+ public string PublicKey { get; set; }
+ }
+}
diff --git a/src/Core/Models/Response/ProfileOrganizationResponse.cs b/src/Core/Models/Response/ProfileOrganizationResponse.cs
index 504a75db8..b83f0e295 100644
--- a/src/Core/Models/Response/ProfileOrganizationResponse.cs
+++ b/src/Core/Models/Response/ProfileOrganizationResponse.cs
@@ -24,5 +24,6 @@ namespace Bit.Core.Models.Response
public OrganizationUserType Type { get; set; }
public bool Enabled { get; set; }
public Permissions Permissions { get; set; } = new Permissions();
+ public string Identifier { get; set; }
}
}
diff --git a/src/Core/Services/ApiService.cs b/src/Core/Services/ApiService.cs
index 78a06e80d..e0a6fa6ae 100644
--- a/src/Core/Services/ApiService.cs
+++ b/src/Core/Services/ApiService.cs
@@ -177,7 +177,7 @@ namespace Bit.Core.Services
return SendAsync(HttpMethod.Post, "/accounts/verify-password", request,
true, false);
}
-
+
#endregion
#region Folder APIs
@@ -402,6 +402,26 @@ namespace Bit.Core.Services
string.Concat("/hibp/breach?username=", username), null, true, true);
}
+ #endregion
+
+ #region Organizations APIs
+
+ public Task GetOrganizationKeysAsync(string id)
+ {
+ return SendAsync