mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Add support for different login methods (#762)
This commit is contained in:
parent
c977f7617a
commit
410e3072fa
8 changed files with 167 additions and 21 deletions
|
@ -24,12 +24,18 @@ interface IdentityApi {
|
|||
@Field(value = "client_id") clientId: String,
|
||||
@Field(value = "username") email: String,
|
||||
@Header(value = "auth-email") authEmail: String,
|
||||
@Field(value = "password") passwordHash: String,
|
||||
@Field(value = "password") passwordHash: String?,
|
||||
@Field(value = "deviceIdentifier") deviceIdentifier: String,
|
||||
@Field(value = "deviceName") deviceName: String,
|
||||
@Field(value = "deviceType") deviceType: String,
|
||||
@Field(value = "grant_type") grantType: String,
|
||||
@Field(value = "captchaResponse") captchaResponse: String?,
|
||||
@Field(value = "code") ssoCode: String?,
|
||||
@Field(value = "code_verifier") ssoCodeVerifier: String?,
|
||||
@Field(value = "redirect_uri") ssoRedirectUri: String?,
|
||||
@Field(value = "twoFactorToken") twoFactorCode: String?,
|
||||
@Field(value = "twoFactorProvider") twoFactorMethod: String?,
|
||||
@Field(value = "twoFactorRemember") twoFactorRemember: String?,
|
||||
): Result<GetTokenResponseJson.Success>
|
||||
|
||||
@GET("/account/prevalidate")
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
/**
|
||||
* Hold the authentication information for different login methods.
|
||||
*/
|
||||
sealed class IdentityTokenAuthModel {
|
||||
/**
|
||||
* The type of authentication.
|
||||
*/
|
||||
abstract val grantType: String
|
||||
|
||||
/**
|
||||
* The username for login with password.
|
||||
*/
|
||||
abstract val username: String?
|
||||
|
||||
/**
|
||||
* The password for login with password.
|
||||
*/
|
||||
abstract val password: String?
|
||||
|
||||
/**
|
||||
* The sso code for login with single sign on.
|
||||
*/
|
||||
abstract val ssoCode: String?
|
||||
|
||||
/**
|
||||
* The sso code verifier for login with single sign on.
|
||||
*/
|
||||
abstract val ssoCodeVerifier: String?
|
||||
|
||||
/**
|
||||
* The sso redirect uri for login with single sign on.
|
||||
*/
|
||||
abstract val ssoRedirectUri: String?
|
||||
|
||||
/**
|
||||
* The data for logging in with a username and password.
|
||||
*/
|
||||
data class MasterPassword(
|
||||
override val username: String,
|
||||
override val password: String,
|
||||
) : IdentityTokenAuthModel() {
|
||||
override val grantType: String get() = "password"
|
||||
override val ssoCode: String? get() = null
|
||||
override val ssoCodeVerifier: String? get() = null
|
||||
override val ssoRedirectUri: String? get() = null
|
||||
}
|
||||
|
||||
/**
|
||||
* The data for logging in with single sign on credentials.
|
||||
*/
|
||||
data class SingleSignOn(
|
||||
override val ssoCode: String,
|
||||
override val ssoCodeVerifier: String,
|
||||
override val ssoRedirectUri: String,
|
||||
) : IdentityTokenAuthModel() {
|
||||
override val grantType: String get() = "authorization_code"
|
||||
override val username: String? get() = null
|
||||
override val password: String? get() = null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
/**
|
||||
* Hold the information necessary to add two-factor authorization
|
||||
* to a login request.
|
||||
*
|
||||
* @property code The two-factor code.
|
||||
* @property method The two-factor method.
|
||||
* @property remember The two-factor remember setting.
|
||||
*/
|
||||
data class TwoFactorDataModel(
|
||||
val code: String,
|
||||
val method: String,
|
||||
val remember: Boolean,
|
||||
)
|
|
@ -1,8 +1,10 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
|
||||
/**
|
||||
* Provides an API for querying identity endpoints.
|
||||
|
@ -14,14 +16,18 @@ interface IdentityService {
|
|||
*
|
||||
* @param uniqueAppId applications unique identifier.
|
||||
* @param email user's email address.
|
||||
* @param passwordHash password hashed with the Bitwarden SDK.
|
||||
* @param authModel information necessary to authenticate with any
|
||||
* of the available login methods.
|
||||
* @param captchaToken captcha token to be passed to the API (nullable).
|
||||
* @param twoFactorData the two-factor data, if applicable.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun getToken(
|
||||
uniqueAppId: String,
|
||||
email: String,
|
||||
passwordHash: String,
|
||||
authModel: IdentityTokenAuthModel,
|
||||
captchaToken: String?,
|
||||
twoFactorData: TwoFactorDataModel? = null,
|
||||
): Result<GetTokenResponseJson>
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,8 +2,10 @@ 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.IdentityTokenAuthModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.executeForResult
|
||||
|
@ -21,8 +23,9 @@ class IdentityServiceImpl constructor(
|
|||
override suspend fun getToken(
|
||||
uniqueAppId: String,
|
||||
email: String,
|
||||
passwordHash: String,
|
||||
authModel: IdentityTokenAuthModel,
|
||||
captchaToken: String?,
|
||||
twoFactorData: TwoFactorDataModel?,
|
||||
): Result<GetTokenResponseJson> = api
|
||||
.getToken(
|
||||
scope = "api+offline_access",
|
||||
|
@ -31,9 +34,15 @@ class IdentityServiceImpl constructor(
|
|||
deviceIdentifier = uniqueAppId,
|
||||
deviceName = deviceModelProvider.deviceModel,
|
||||
deviceType = "0",
|
||||
grantType = "password",
|
||||
passwordHash = passwordHash,
|
||||
grantType = authModel.grantType,
|
||||
passwordHash = authModel.password,
|
||||
email = email,
|
||||
ssoCode = authModel.ssoCode,
|
||||
ssoCodeVerifier = authModel.ssoCodeVerifier,
|
||||
ssoRedirectUri = authModel.ssoRedirectUri,
|
||||
twoFactorCode = twoFactorData?.code,
|
||||
twoFactorMethod = twoFactorData?.method,
|
||||
twoFactorRemember = twoFactorData?.remember?.let { if (it) "1" else "0 " },
|
||||
captchaResponse = captchaToken,
|
||||
)
|
||||
.recoverCatching { throwable ->
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson.CaptchaRequired
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson.Success
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
|
@ -194,7 +195,10 @@ class AuthRepositoryImpl(
|
|||
identityService.getToken(
|
||||
uniqueAppId = authDiskSource.uniqueAppId,
|
||||
email = email,
|
||||
passwordHash = passwordHash,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = email,
|
||||
password = passwordHash,
|
||||
),
|
||||
captchaToken = captchaToken,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ 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.IdentityTokenAuthModel
|
||||
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
|
||||
|
@ -40,7 +41,10 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
server.enqueue(MockResponse().setBody(LOGIN_SUCCESS_JSON))
|
||||
val result = identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -52,7 +56,10 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
server.enqueue(MockResponse().setResponseCode(500))
|
||||
val result = identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -64,7 +71,10 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
server.enqueue(MockResponse().setResponseCode(400).setBody(CAPTCHA_BODY_JSON))
|
||||
val result = identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -76,7 +86,10 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
server.enqueue(MockResponse().setResponseCode(400).setBody(INVALID_LOGIN_JSON))
|
||||
val result = identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
|||
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
|
@ -444,7 +445,10 @@ class AuthRepositoryTest {
|
|||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -457,7 +461,10 @@ class AuthRepositoryTest {
|
|||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -472,7 +479,10 @@ class AuthRepositoryTest {
|
|||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -491,7 +501,10 @@ class AuthRepositoryTest {
|
|||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -509,7 +522,10 @@ class AuthRepositoryTest {
|
|||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -548,7 +564,10 @@ class AuthRepositoryTest {
|
|||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -587,7 +606,10 @@ class AuthRepositoryTest {
|
|||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -628,7 +650,10 @@ class AuthRepositoryTest {
|
|||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -658,7 +683,10 @@ class AuthRepositoryTest {
|
|||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
@ -671,7 +699,10 @@ class AuthRepositoryTest {
|
|||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
authModel = IdentityTokenAuthModel.MasterPassword(
|
||||
username = EMAIL,
|
||||
password = PASSWORD_HASH,
|
||||
),
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue