BIT-765: Add additional properties to GetTokenResponseJson.Success (#136)

This commit is contained in:
Brian Yencho 2023-10-19 09:09:21 -05:00 committed by Álison Fernandes
parent 1d2f23d426
commit ab2a500607
7 changed files with 250 additions and 5 deletions

View file

@ -10,12 +10,65 @@ sealed class GetTokenResponseJson {
/**
* Models json response of the get token request.
*
* @param accessToken the access token.
* @property accessToken The user's access token.
* @property refreshToken The user's refresh token.
* @property tokenType The type of token (ex: "Bearer").
* @property expiresInSeconds The amount of time (in seconds) before the [accessToken] expires.
* @property key The user's key.
* @property privateKey The user's private key.
* @property kdfType The KDF type.
* @property kdfIterations The number of iterations when calculating a user's password.
* @property kdfMemory The amount of memory to use when calculating a password hash (MB).
* @property kdfParallelism The number of threads to use when calculating a password hash.
* @property shouldForcePasswordReset Whether or not the app must force a password reset.
* @property shouldResetMasterPassword Whether or not the user is required to reset their
* master password.
* @property masterPasswordPolicyOptions The options available for a user's master password.
* @property userDecryptionOptions The options available to a user for decryption.
*/
@Serializable
data class Success(
@SerialName("access_token")
val accessToken: String,
@SerialName("refresh_token")
val refreshToken: String,
@SerialName("token_type")
val tokenType: String,
@SerialName("expires_in")
val expiresInSeconds: Int,
@SerialName("Key")
val key: String,
@SerialName("PrivateKey")
val privateKey: String,
@SerialName("Kdf")
val kdfType: KdfTypeJson,
@SerialName("KdfIterations")
val kdfIterations: Int?,
@SerialName("KdfMemory")
val kdfMemory: Int?,
@SerialName("KdfParallelism")
val kdfParallelism: Int?,
@SerialName("ForcePasswordReset")
val shouldForcePasswordReset: Boolean,
@SerialName("ResetMasterPassword")
val shouldResetMasterPassword: Boolean,
@SerialName("MasterPasswordPolicy")
val masterPasswordPolicyOptions: MasterPasswordPolicyOptionsJson?,
@SerialName("UserDecryptionOptions")
val userDecryptionOptions: UserDecryptionOptionsJson?,
) : GetTokenResponseJson()
/**

View file

@ -0,0 +1,15 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Decryption options related to a user's key connector.
*
* @property keyConnectorUrl URL to the user's key connector.
*/
@Serializable
data class KeyConnectorUserDecryptionOptionsJson(
@SerialName("KeyConnectorUrl")
val keyConnectorUrl: String,
)

View file

@ -0,0 +1,39 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Policies that may be applied to a master password.
*
* @property minimumComplexity The minimum required password complexity (if applicable).
* @property minimumLength The minimum required password length (if applicable).
* @property shouldRequireUppercase Whether or not uppercase characters should be required.
* @property shouldRequireLowercase Whether or not lowercase characters should be required.
* @property shouldRequireNumbers Whether or not numbers should be required.
* @property shouldRequireSpecialCharacters Whether or not special characters should be required.
* @property shouldEnforceOnLogin Whether or not the restrictions should be enforced on login.
*/
@Serializable
data class MasterPasswordPolicyOptionsJson(
@SerialName("MinComplexity")
val minimumComplexity: Int?,
@SerialName("MinLength")
val minimumLength: Int?,
@SerialName("RequireUpper")
val shouldRequireUppercase: Boolean?,
@SerialName("RequireLower")
val shouldRequireLowercase: Boolean?,
@SerialName("RequireNumbers")
val shouldRequireNumbers: Boolean?,
@SerialName("RequireSpecial")
val shouldRequireSpecialCharacters: Boolean?,
@SerialName("EnforceOnLogin")
val shouldEnforceOnLogin: Boolean?,
)

View file

@ -0,0 +1,32 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Decryption options related to a user's trusted device.
*
* @property encryptedPrivateKey The user's encrypted private key.
* @property encryptedUserKey The user's encrypted key.
* @property hasAdminApproval Whether or not the user has admin approval.
* @property hasLoginApprovingDevice Whether or not the user has a login approving device.
* @property hasManageResetPasswordPermission Whether or not the user has manage reset password
* permission.
*/
@Serializable
data class TrustedDeviceUserDecryptionOptionsJson(
@SerialName("EncryptedPrivateKey")
val encryptedPrivateKey: String?,
@SerialName("EncryptedUserKey")
val encryptedUserKey: String?,
@SerialName("HasAdminApproval")
val hasAdminApproval: Boolean,
@SerialName("HasLoginApprovingDevice")
val hasLoginApprovingDevice: Boolean,
@SerialName("HasManageResetPasswordPermission")
val hasManageResetPasswordPermission: Boolean,
)

View file

@ -0,0 +1,25 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* The options available to a user for decryption.
*
* @property hasMasterPassword Whether the current user has a master password that can be used to
* decrypt their vault.
* @property trustedDeviceUserDecryptionOptions Decryption options related to a user's trusted
* device.
* @property keyConnectorUserDecryptionOptions Decryption options related to a user's key connector.
*/
@Serializable
data class UserDecryptionOptionsJson(
@SerialName("HasMasterPassword")
val hasMasterPassword: Boolean,
@SerialName("TrustedDeviceOption")
val trustedDeviceUserDecryptionOptions: TrustedDeviceUserDecryptionOptionsJson?,
@SerialName("KeyConnectorOption")
val keyConnectorUserDecryptionOptions: KeyConnectorUserDecryptionOptionsJson?,
)

View file

@ -2,6 +2,11 @@ package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.api.IdentityApi
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorUserDecryptionOptionsJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.MasterPasswordPolicyOptionsJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
import com.x8bit.bitwarden.data.platform.util.DeviceModelProvider
import io.mockk.every
@ -87,10 +92,79 @@ private val CAPTCHA_BODY = GetTokenResponseJson.CaptchaRequired("123")
private const val LOGIN_SUCCESS_JSON = """
{
"access_token": "123"
"access_token": "accessToken",
"expires_in": 3600,
"token_type": "Bearer",
"refresh_token": "refreshToken",
"PrivateKey": "privateKey",
"Key": "key",
"MasterPasswordPolicy": {
"MinComplexity": 10,
"MinLength": 100,
"RequireUpper": true,
"RequireLower": true,
"RequireNumbers": true,
"RequireSpecial": true,
"EnforceOnLogin": true
},
"ForcePasswordReset": true,
"ResetMasterPassword": true,
"Kdf": 1,
"KdfIterations": 600000,
"KdfMemory": 16,
"KdfParallelism": 4,
"UserDecryptionOptions": {
"HasMasterPassword": true,
"TrustedDeviceOption": {
"EncryptedPrivateKey": "encryptedPrivateKey",
"EncryptedUserKey": "encryptedUserKey",
"HasAdminApproval": true,
"HasLoginApprovingDevice": true,
"HasManageResetPasswordPermission": true
},
"KeyConnectorOption": {
"KeyConnectorUrl": "keyConnectorUrl"
}
}
}
"""
private val LOGIN_SUCCESS = GetTokenResponseJson.Success("123")
private val LOGIN_SUCCESS = GetTokenResponseJson.Success(
accessToken = "accessToken",
refreshToken = "refreshToken",
tokenType = "Bearer",
expiresInSeconds = 3600,
key = "key",
kdfType = KdfTypeJson.ARGON2_ID,
kdfIterations = 600000,
kdfMemory = 16,
kdfParallelism = 4,
privateKey = "privateKey",
shouldForcePasswordReset = true,
shouldResetMasterPassword = true,
masterPasswordPolicyOptions = MasterPasswordPolicyOptionsJson(
minimumComplexity = 10,
minimumLength = 100,
shouldRequireUppercase = true,
shouldRequireLowercase = true,
shouldRequireNumbers = true,
shouldRequireSpecialCharacters = true,
shouldEnforceOnLogin = true,
),
userDecryptionOptions = UserDecryptionOptionsJson(
hasMasterPassword = true,
trustedDeviceUserDecryptionOptions = TrustedDeviceUserDecryptionOptionsJson(
encryptedPrivateKey = "encryptedPrivateKey",
encryptedUserKey = "encryptedUserKey",
hasAdminApproval = true,
hasLoginApprovingDevice = true,
hasManageResetPasswordPermission = true,
),
keyConnectorUserDecryptionOptions = KeyConnectorUserDecryptionOptionsJson(
keyConnectorUrl = "keyConnectorUrl",
),
),
)
private const val INVALID_LOGIN_JSON = """
{

View file

@ -141,6 +141,9 @@ class AuthRepositoryTest {
@Test
fun `login get token succeeds should return Success and update AuthState`() = runTest {
val successResponse = mockk<GetTokenResponseJson.Success> {
every { accessToken } returns ACCESS_TOKEN
}
coEvery {
accountsService.preLogin(email = EMAIL)
} returns Result.success(PRE_LOGIN_SUCCESS)
@ -151,7 +154,7 @@ class AuthRepositoryTest {
captchaToken = null,
)
}
.returns(Result.success(GetTokenResponseJson.Success(accessToken = ACCESS_TOKEN)))
.returns(Result.success(successResponse))
every { authInterceptor.authToken = ACCESS_TOKEN } returns Unit
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
assertEquals(LoginResult.Success, result)
@ -205,6 +208,9 @@ class AuthRepositoryTest {
@Test
fun `logout should change AuthState to be Unauthenticated`() = runTest {
// First login:
val successResponse = mockk<GetTokenResponseJson.Success> {
every { accessToken } returns ACCESS_TOKEN
}
coEvery {
accountsService.preLogin(email = EMAIL)
} returns Result.success(PRE_LOGIN_SUCCESS)
@ -215,7 +221,8 @@ class AuthRepositoryTest {
captchaToken = null,
)
}
.returns(Result.success(GetTokenResponseJson.Success(accessToken = ACCESS_TOKEN)))
.returns(Result.success(successResponse))
every { authInterceptor.authToken = ACCESS_TOKEN } returns Unit
repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)