PM-12683 SSO user needed password set bug (#4018)

This commit is contained in:
Dave Severns 2024-10-03 12:25:58 -04:00 committed by GitHub
parent e6eb626d85
commit fd6b276cc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 391 additions and 4 deletions

View file

@ -137,9 +137,6 @@ fun UserStateJson.toUserState(
it.role == OrganizationType.ADMIN ||
it.shouldManageResetPassword
}
val needsMasterPassword = decryptionOptions?.hasMasterPassword == false &&
hasManageResetPasswordPermission &&
keyConnectorOptions == null
val trustedDevice = trustedDeviceOptions?.let {
UserState.TrustedDevice(
isDeviceTrusted = isDeviceTrustedProvider(userId),
@ -148,7 +145,14 @@ fun UserStateJson.toUserState(
hasResetPasswordPermission = it.hasManageResetPasswordPermission,
)
}
// If a user does not have a Master Password we want to check if they have another
// method for unlocking the vault. In the case of a TDE user we check if they
// have the reset password permission via their organization(S). If the user does
// not belong to a TDE or we check to see if they user key connector.
val tdeUserNeedsMasterPassword =
hasManageResetPasswordPermission.takeIf { trustedDevice != null }
val needsMasterPassword = decryptionOptions?.hasMasterPassword == false &&
(tdeUserNeedsMasterPassword ?: (keyConnectorOptions == null))
UserState.Account(
userId = userId,
name = profile.name,

View file

@ -865,4 +865,387 @@ class UserStateJsonExtensionsTest {
),
)
}
@Suppress("MaxLineLength")
@Test
fun `toUserState should set true for needsMasterPassword for TDE user with permission through organization`() {
assertEquals(
UserState(
activeUserId = "activeUserId",
accounts = listOf(
UserState.Account(
userId = "activeUserId",
name = "activeName",
email = "activeEmail",
avatarColorHex = "#ffecbc49",
environment = Environment.Eu,
isPremium = true,
isLoggedIn = false,
isVaultUnlocked = false,
needsPasswordReset = false,
organizations = listOf(
Organization(
id = "organizationId",
name = "organizationName",
// Key part of the result #1, this is true or the role is owner or
// admin
shouldManageResetPassword = true,
shouldUseKeyConnector = false,
role = OrganizationType.USER,
),
),
isBiometricsEnabled = false,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
needsMasterPassword = true,
// Key part of the result #2, TDE options should exist
trustedDevice = UserState.TrustedDevice(
isDeviceTrusted = true,
hasAdminApproval = false,
hasLoginApprovingDevice = true,
hasResetPasswordPermission = false,
),
// Key part of the result #3, options should have false for
// hasMasterPassword
hasMasterPassword = false,
isUsingKeyConnector = true,
onboardingStatus = OnboardingStatus.COMPLETE,
),
),
hasPendingAccountAddition = true,
),
UserStateJson(
activeUserId = "activeUserId",
accounts = mapOf(
"activeUserId" to AccountJson(
profile = mockk {
every { userId } returns "activeUserId"
every { name } returns "activeName"
every { email } returns "activeEmail"
every { avatarColorHex } returns null
every { hasPremium } returns true
every { forcePasswordResetReason } returns null
every { userDecryptionOptions } returns UserDecryptionOptionsJson(
hasMasterPassword = false,
trustedDeviceUserDecryptionOptions = TrustedDeviceUserDecryptionOptionsJson(
encryptedPrivateKey = null,
encryptedUserKey = null,
hasAdminApproval = false,
hasLoginApprovingDevice = true,
hasManageResetPasswordPermission = false,
),
keyConnectorUserDecryptionOptions = null,
)
},
tokens = null,
settings = AccountJson.Settings(
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_EU,
),
),
),
)
.toUserState(
vaultState = emptyList(),
userAccountTokens = listOf(
UserAccountTokens(
userId = "activeUserId",
accessToken = null,
refreshToken = null,
),
),
userOrganizationsList = listOf(
UserOrganizations(
userId = "activeUserId",
organizations = listOf(
Organization(
id = "organizationId",
name = "organizationName",
shouldManageResetPassword = true,
shouldUseKeyConnector = false,
role = OrganizationType.USER,
),
),
),
),
userIsUsingKeyConnectorList = listOf(
UserKeyConnectorState(
userId = "activeUserId",
isUsingKeyConnector = true,
),
),
hasPendingAccountAddition = true,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isDeviceTrustedProvider = { true },
onboardingStatus = null,
),
)
}
@Test
fun `toUserState should set true for needsMasterPassword for SSO user with no key connector`() {
assertEquals(
UserState(
activeUserId = "activeUserId",
accounts = listOf(
UserState.Account(
userId = "activeUserId",
name = "activeName",
email = "activeEmail",
avatarColorHex = "#ffecbc49",
environment = Environment.Eu,
isPremium = false,
isLoggedIn = false,
isVaultUnlocked = false,
needsPasswordReset = false,
organizations = emptyList(),
isBiometricsEnabled = false,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
needsMasterPassword = false,
trustedDevice = null,
hasMasterPassword = false,
isUsingKeyConnector = false,
onboardingStatus = OnboardingStatus.COMPLETE,
),
),
hasPendingAccountAddition = true,
),
UserStateJson(
activeUserId = "activeUserId",
accounts = mapOf(
"activeUserId" to AccountJson(
profile = mockk {
every { userId } returns "activeUserId"
every { name } returns "activeName"
every { email } returns "activeEmail"
every { avatarColorHex } returns null
every { hasPremium } returns false
every { forcePasswordResetReason } returns null
// The decryption options are what are determining the result
@Suppress("MaxLineLength")
every { userDecryptionOptions } returns UserDecryptionOptionsJson(
hasMasterPassword = false,
trustedDeviceUserDecryptionOptions = null,
keyConnectorUserDecryptionOptions = KeyConnectorUserDecryptionOptionsJson(
keyConnectorUrl = "keyConnectorUrl",
),
)
},
tokens = null,
settings = AccountJson.Settings(
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_EU,
),
),
),
)
.toUserState(
vaultState = emptyList(),
userAccountTokens = listOf(
UserAccountTokens(
userId = "activeUserId",
accessToken = null,
refreshToken = null,
),
),
userOrganizationsList = emptyList(),
userIsUsingKeyConnectorList = emptyList(),
hasPendingAccountAddition = true,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isDeviceTrustedProvider = { true },
onboardingStatus = null,
),
)
}
@Test
fun `toUserState should set false for needsMasterPassword for SSO user with key connector`() {
assertEquals(
UserState(
activeUserId = "activeUserId",
accounts = listOf(
UserState.Account(
userId = "activeUserId",
name = "activeName",
email = "activeEmail",
avatarColorHex = "#ffecbc49",
environment = Environment.Eu,
isPremium = false,
isLoggedIn = false,
isVaultUnlocked = false,
needsPasswordReset = false,
organizations = emptyList(),
isBiometricsEnabled = false,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
needsMasterPassword = true,
trustedDevice = null,
hasMasterPassword = false,
isUsingKeyConnector = true,
onboardingStatus = OnboardingStatus.COMPLETE,
),
),
hasPendingAccountAddition = true,
),
UserStateJson(
activeUserId = "activeUserId",
accounts = mapOf(
"activeUserId" to AccountJson(
profile = mockk {
every { userId } returns "activeUserId"
every { name } returns "activeName"
every { email } returns "activeEmail"
every { avatarColorHex } returns null
every { hasPremium } returns false
every { forcePasswordResetReason } returns null
// The decryption options are what are determining the result
every { userDecryptionOptions } returns UserDecryptionOptionsJson(
hasMasterPassword = false,
trustedDeviceUserDecryptionOptions = null,
keyConnectorUserDecryptionOptions = null,
)
},
tokens = null,
settings = AccountJson.Settings(
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_EU,
),
),
),
)
.toUserState(
vaultState = emptyList(),
userAccountTokens = listOf(
UserAccountTokens(
userId = "activeUserId",
accessToken = null,
refreshToken = null,
),
),
userOrganizationsList = emptyList(),
userIsUsingKeyConnectorList = listOf(
UserKeyConnectorState(
userId = "activeUserId",
isUsingKeyConnector = true,
),
),
hasPendingAccountAddition = true,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isDeviceTrustedProvider = { true },
onboardingStatus = null,
),
)
}
@Suppress("MaxLineLength")
@Test
fun `toUserState should set false for needsMasterPassword for SSO user with TDE but no permission via organization`() {
assertEquals(
UserState(
activeUserId = "activeUserId",
accounts = listOf(
UserState.Account(
userId = "activeUserId",
name = "activeName",
email = "activeEmail",
avatarColorHex = "#ffecbc49",
environment = Environment.Eu,
isPremium = false,
isLoggedIn = false,
isVaultUnlocked = false,
needsPasswordReset = false,
organizations = listOf(
Organization(
id = "organizationId",
name = "organizationName",
// Key part of the result #1, this is true or the role is owner or
// admin
shouldManageResetPassword = false,
shouldUseKeyConnector = false,
role = OrganizationType.USER,
),
),
isBiometricsEnabled = false,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
needsMasterPassword = false,
trustedDevice = UserState.TrustedDevice(
isDeviceTrusted = true,
hasAdminApproval = false,
hasLoginApprovingDevice = true,
hasResetPasswordPermission = false,
),
hasMasterPassword = false,
isUsingKeyConnector = true,
onboardingStatus = OnboardingStatus.COMPLETE,
),
),
hasPendingAccountAddition = true,
),
UserStateJson(
activeUserId = "activeUserId",
accounts = mapOf(
"activeUserId" to AccountJson(
profile = mockk {
every { userId } returns "activeUserId"
every { name } returns "activeName"
every { email } returns "activeEmail"
every { avatarColorHex } returns null
every { hasPremium } returns false
every { forcePasswordResetReason } returns null
// The decryption options are what are determining the result
@Suppress("MaxLineLength")
every { userDecryptionOptions } returns UserDecryptionOptionsJson(
hasMasterPassword = false,
trustedDeviceUserDecryptionOptions = TrustedDeviceUserDecryptionOptionsJson(
encryptedPrivateKey = null,
encryptedUserKey = null,
hasAdminApproval = false,
hasLoginApprovingDevice = true,
hasManageResetPasswordPermission = false,
),
keyConnectorUserDecryptionOptions = null,
)
},
tokens = null,
settings = AccountJson.Settings(
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_EU,
),
),
),
)
.toUserState(
vaultState = emptyList(),
userAccountTokens = listOf(
UserAccountTokens(
userId = "activeUserId",
accessToken = null,
refreshToken = null,
),
),
userOrganizationsList = listOf(
UserOrganizations(
userId = "activeUserId",
organizations = listOf(
Organization(
id = "organizationId",
name = "organizationName",
shouldManageResetPassword = false,
shouldUseKeyConnector = false,
role = OrganizationType.USER,
),
),
),
),
userIsUsingKeyConnectorList = listOf(
UserKeyConnectorState(
userId = "activeUserId",
isUsingKeyConnector = true,
),
),
hasPendingAccountAddition = true,
isBiometricsEnabledProvider = { false },
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
isDeviceTrustedProvider = { true },
onboardingStatus = null,
),
)
}
}