Skip to content

Commit

Permalink
[PM-3273][PM-4679] New owner/admin permission on login (#2837)
Browse files Browse the repository at this point in the history
* [PM-3273] Add property for password set. Add labels. Update sync service.

* [PM-3273] Set password needs set in state. Read value on sync and nav to page.

* [PM-3273] Add navigation to Set Password on vault landing if needed.

* [PM-3273] Update SetPasswordPage copy

* [PM-3273] Add ManageResetPassword to Org Permissions, handle it on sync.

* [PM-3273] Change user has master password state when set master password is complete.

* [PM-3273] Code clean up

* [PM-3273] Remove unnecessary property from account profile

* [PM-3273] Add check for remembered org identifier

* [PM-4679] Added logging calls for future checks.

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
  • Loading branch information
andrebispo5 and fedemkr committed Nov 9, 2023
1 parent 3a13ba4 commit 793c5fe
Show file tree
Hide file tree
Showing 42 changed files with 203,544 additions and 33 deletions.
5 changes: 5 additions & 0 deletions src/App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ public App(AppOptions appOptions)
new NavigationPage(new UpdateTempPasswordPage()));
});
}
else if (message.Command == Constants.ForceSetPassword)
{
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
Expand Down
13 changes: 6 additions & 7 deletions src/App/Pages/Accounts/LoginSsoPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,18 @@ private async Task LogIn(string code, string codeVerifier, string orgId)
StartDeviceApprovalOptionsAction?.Invoke();
return;
}
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
return;
}
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission);
}
// Device is trusted and has keys, so we can decrypt
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
Expand Down
2 changes: 1 addition & 1 deletion src/App/Pages/Accounts/SetPasswordPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<StackLayout Spacing="20">
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label Text="{u:I18n SetMasterPasswordSummary}"
<Label Text="{Binding SetMasterPasswordSummary}"
StyleClass="text-md"
HorizontalTextAlignment="Start"></Label>
</StackLayout>
Expand Down
48 changes: 37 additions & 11 deletions src/App/Pages/Accounts/SetPasswordPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
Expand All @@ -28,6 +27,7 @@ public class SetPasswordPageViewModel : BaseViewModel
private readonly IPolicyService _policyService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly II18nService _i18nService;
private readonly ISyncService _syncService;

private bool _showPassword;
private bool _isPolicyInEffect;
Expand All @@ -46,6 +46,7 @@ public SetPasswordPageViewModel()
_passwordGenerationService =
ServiceContainer.Resolve<IPasswordGenerationService>("passwordGenerationService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_syncService = ServiceContainer.Resolve<ISyncService>();

PageTitle = AppResources.SetMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
Expand Down Expand Up @@ -100,11 +101,17 @@ public MasterPasswordPolicyOptions Policy
public Action CloseAction { get; set; }
public string OrgIdentifier { get; set; }
public string OrgId { get; set; }
public ForcePasswordResetReason? ForceSetPasswordReason { get; private set; }

public string SetMasterPasswordSummary => ForceSetPasswordReason == ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission
? AppResources.YourOrganizationPermissionsWereUpdatedRequeringYouToSetAMasterPassword
: AppResources.YourOrganizationRequiresYouToSetAMasterPassword;

public async Task InitAsync()
{
await CheckPasswordPolicy();

ForceSetPasswordReason = await _stateService.GetForcePasswordResetReasonAsync();
TriggerPropertyChanged(nameof(SetMasterPasswordSummary));
try
{
var response = await _apiService.GetOrganizationAutoEnrollStatusAsync(OrgIdentifier);
Expand Down Expand Up @@ -171,8 +178,7 @@ public async Task SubmitAsync()

var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());

var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var keysRequest = await GetKeysForSetPasswordRequestAsync(newUserKey);
var request = new SetPasswordRequest
{
MasterPasswordHash = masterPasswordHash,
Expand All @@ -183,24 +189,26 @@ public async Task SubmitAsync()
KdfMemory = kdfConfig.Memory,
KdfParallelism = kdfConfig.Parallelism,
OrgIdentifier = OrgIdentifier,
Keys = new KeysRequest
{
PublicKey = newPublicKey,
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
}
Keys = keysRequest
};

try
{
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
// Set Password and relevant information
await _apiService.SetPasswordAsync(request);
await _stateService.SetKdfConfigurationAsync(kdfConfig);
await _cryptoService.SetUserKeyAsync(newUserKey);
await _cryptoService.SetMasterKeyAsync(newMasterKey);
await _cryptoService.SetMasterKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
await _cryptoService.SetUserPrivateKeyAsync(newProtectedPrivateKey.EncryptedString);

// Set private key only for new JIT provisioned users in MP encryption orgs
// Existing TDE users will have private key set on sync or on login
if (keysRequest != null)
{
await _cryptoService.SetUserPrivateKeyAsync(keysRequest.EncryptedPrivateKey);
}

if (ResetPasswordAutoEnroll)
{
Expand All @@ -221,6 +229,9 @@ public async Task SubmitAsync()
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(OrgId, userId, resetRequest);
}

await _stateService.SetForcePasswordResetReasonAsync(null);
await _stateService.SetUserHasMasterPasswordAsync(true);
await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync();
SetPasswordSuccessAction?.Invoke();
}
Expand All @@ -235,6 +246,21 @@ public async Task SubmitAsync()
}
}

private async Task<KeysRequest> GetKeysForSetPasswordRequestAsync(UserKey newUserKey)
{
if (ForceSetPasswordReason == ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission)
{
return null;
}

var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
return new KeysRequest
{
PublicKey = newPublicKey,
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
};
}

public void TogglePassword()
{
ShowPassword = !ShowPassword;
Expand Down
15 changes: 7 additions & 8 deletions src/App/Pages/Accounts/TwoFactorPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities;
Expand Down Expand Up @@ -335,20 +336,18 @@ public async Task SubmitAsync(bool showLoading = true)
StartDeviceApprovalOptionsAction?.Invoke();
return;
}
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
return;
}
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}

// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission);
}
// Device is trusted and has keys, so we can decrypt
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
Expand Down
35 changes: 32 additions & 3 deletions src/App/Pages/TabsPage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Effects;
using Bit.App.Models;
Expand All @@ -7,6 +8,7 @@
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.Forms;

Expand Down Expand Up @@ -100,11 +102,38 @@ protected override async void OnAppearing()
_messagingService.Send("convertAccountToKeyConnector");
}

var forcePasswordResetReason = await _stateService.GetForcePasswordResetReasonAsync();
await ForcePasswordResetIfNeededAsync();
}

if (forcePasswordResetReason.HasValue)
private async Task ForcePasswordResetIfNeededAsync()
{
var forcePasswordResetReason = await _stateService.GetForcePasswordResetReasonAsync();
switch (forcePasswordResetReason)
{
_messagingService.Send(Constants.ForceUpdatePassword);
case ForcePasswordResetReason.TdeUserWithoutPasswordHasPasswordResetPermission:
// TDE users should only have one org
var userOrgs = await _stateService.GetOrganizationsAsync();
if (userOrgs != null && userOrgs.Any())
{
_messagingService.Send(Constants.ForceSetPassword, userOrgs.First().Value.Identifier);
return;
}
_logger.Value.Error("TDE user needs to set password but has no organizations.");

var rememberedOrg = _stateService.GetRememberedOrgIdentifierAsync();
if (rememberedOrg == null)
{
_logger.Value.Error("TDE user needs to set password but has no organizations or remembered org identifier.");
return;
}
_messagingService.Send(Constants.ForceSetPassword, rememberedOrg);
return;
case ForcePasswordResetReason.AdminForcePasswordReset:
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
_messagingService.Send(Constants.ForceUpdatePassword);
break;
default:
return;
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/App/Resources/AppResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 793c5fe

Please sign in to comment.