mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Add trusted device API (#1183)
This commit is contained in:
parent
9253a7b682
commit
de6f31775b
8 changed files with 144 additions and 10 deletions
|
@ -0,0 +1,18 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.PUT
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines raw calls under the /devices API that require authentication.
|
||||||
|
*/
|
||||||
|
interface AuthenticatedDevicesApi {
|
||||||
|
@PUT("/devices/{appId}/keys")
|
||||||
|
suspend fun updateTrustedDeviceKeys(
|
||||||
|
@Path(value = "appId") appId: String,
|
||||||
|
@Body request: TrustedDeviceKeysRequestJson,
|
||||||
|
): Result<TrustedDeviceKeysResponseJson>
|
||||||
|
}
|
|
@ -4,10 +4,9 @@ import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines raw calls under the /devices API.
|
* Defines raw calls under the /devices API that do not require authentication.
|
||||||
*/
|
*/
|
||||||
interface DevicesApi {
|
interface UnauthenticatedDevicesApi {
|
||||||
|
|
||||||
@GET("/devices/knowndevice")
|
@GET("/devices/knowndevice")
|
||||||
suspend fun getIsKnownDevice(
|
suspend fun getIsKnownDevice(
|
||||||
@Header(value = "X-Request-Email") emailAddress: String,
|
@Header(value = "X-Request-Email") emailAddress: String,
|
|
@ -54,7 +54,8 @@ object AuthNetworkModule {
|
||||||
fun providesDevicesService(
|
fun providesDevicesService(
|
||||||
retrofits: Retrofits,
|
retrofits: Retrofits,
|
||||||
): DevicesService = DevicesServiceImpl(
|
): DevicesService = DevicesServiceImpl(
|
||||||
devicesApi = retrofits.unauthenticatedApiRetrofit.create(),
|
authenticatedDevicesApi = retrofits.authenticatedApiRetrofit.create(),
|
||||||
|
unauthenticatedDevicesApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request body for trusting a device.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class TrustedDeviceKeysRequestJson(
|
||||||
|
@SerialName("EncryptedUserKey") val encryptedUserKey: String,
|
||||||
|
@SerialName("EncryptedPublicKey") val encryptedDevicePublicKey: String,
|
||||||
|
@SerialName("EncryptedPrivateKey") val encryptedDevicePrivateKey: String,
|
||||||
|
)
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response body for trusting a device.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class TrustedDeviceKeysResponseJson(
|
||||||
|
@SerialName("id") val id: String,
|
||||||
|
@SerialName("name") val name: String,
|
||||||
|
@SerialName("identifier") val identifier: String,
|
||||||
|
@SerialName("type") val type: Int,
|
||||||
|
@Contextual @SerialName("creationDate") val creationDate: ZonedDateTime,
|
||||||
|
)
|
|
@ -1,5 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an API for interacting with the /devices endpoints.
|
* Provides an API for interacting with the /devices endpoints.
|
||||||
*/
|
*/
|
||||||
|
@ -11,4 +13,14 @@ interface DevicesService {
|
||||||
emailAddress: String,
|
emailAddress: String,
|
||||||
deviceId: String,
|
deviceId: String,
|
||||||
): Result<Boolean>
|
): Result<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establishes trust with this device by storing the encrypted keys in the cloud.
|
||||||
|
*/
|
||||||
|
suspend fun trustDevice(
|
||||||
|
appId: String,
|
||||||
|
encryptedUserKey: String,
|
||||||
|
encryptedDevicePublicKey: String,
|
||||||
|
encryptedDevicePrivateKey: String,
|
||||||
|
): Result<TrustedDeviceKeysResponseJson>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,34 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.DevicesApi
|
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthenticatedDevicesApi
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.api.UnauthenticatedDevicesApi
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
||||||
|
|
||||||
class DevicesServiceImpl(
|
class DevicesServiceImpl(
|
||||||
private val devicesApi: DevicesApi,
|
private val authenticatedDevicesApi: AuthenticatedDevicesApi,
|
||||||
|
private val unauthenticatedDevicesApi: UnauthenticatedDevicesApi,
|
||||||
) : DevicesService {
|
) : DevicesService {
|
||||||
override suspend fun getIsKnownDevice(
|
override suspend fun getIsKnownDevice(
|
||||||
emailAddress: String,
|
emailAddress: String,
|
||||||
deviceId: String,
|
deviceId: String,
|
||||||
): Result<Boolean> = devicesApi.getIsKnownDevice(
|
): Result<Boolean> = unauthenticatedDevicesApi.getIsKnownDevice(
|
||||||
emailAddress = emailAddress.base64UrlEncode(),
|
emailAddress = emailAddress.base64UrlEncode(),
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun trustDevice(
|
||||||
|
appId: String,
|
||||||
|
encryptedUserKey: String,
|
||||||
|
encryptedDevicePublicKey: String,
|
||||||
|
encryptedDevicePrivateKey: String,
|
||||||
|
): Result<TrustedDeviceKeysResponseJson> = authenticatedDevicesApi.updateTrustedDeviceKeys(
|
||||||
|
appId = appId,
|
||||||
|
request = TrustedDeviceKeysRequestJson(
|
||||||
|
encryptedUserKey = encryptedUserKey,
|
||||||
|
encryptedDevicePublicKey = encryptedDevicePublicKey,
|
||||||
|
encryptedDevicePrivateKey = encryptedDevicePrivateKey,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.DevicesApi
|
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthenticatedDevicesApi
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.api.UnauthenticatedDevicesApi
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
||||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||||
|
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import retrofit2.create
|
import retrofit2.create
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
class DevicesServiceTest : BaseServiceTest() {
|
class DevicesServiceTest : BaseServiceTest() {
|
||||||
|
|
||||||
private val devicesApi: DevicesApi = retrofit.create()
|
private val authenticatedDevicesApi: AuthenticatedDevicesApi = retrofit.create()
|
||||||
|
private val unauthenticatedDevicesApi: UnauthenticatedDevicesApi = retrofit.create()
|
||||||
private val service = DevicesServiceImpl(
|
private val service = DevicesServiceImpl(
|
||||||
devicesApi = devicesApi,
|
authenticatedDevicesApi = authenticatedDevicesApi,
|
||||||
|
unauthenticatedDevicesApi = unauthenticatedDevicesApi,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -30,4 +37,51 @@ class DevicesServiceTest : BaseServiceTest() {
|
||||||
val actual = service.getIsKnownDevice("email", "id")
|
val actual = service.getIsKnownDevice("email", "id")
|
||||||
assertTrue(actual.isSuccess)
|
assertTrue(actual.isSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `trustDevice when response is Failure should return Failure`() = runTest {
|
||||||
|
val response = MockResponse().setResponseCode(400)
|
||||||
|
server.enqueue(response)
|
||||||
|
val actual = service.trustDevice(
|
||||||
|
appId = "appId",
|
||||||
|
encryptedUserKey = "encryptedUserKey",
|
||||||
|
encryptedDevicePublicKey = "encryptedDevicePublicKey",
|
||||||
|
encryptedDevicePrivateKey = "encryptedDevicePrivateKey",
|
||||||
|
)
|
||||||
|
assertTrue(actual.isFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `trustDevice when response is Success should return Success`() = runTest {
|
||||||
|
val response = MockResponse().setBody(TRUST_DEVICE_RESPONSE_JSON).setResponseCode(200)
|
||||||
|
server.enqueue(response)
|
||||||
|
val actual = service.trustDevice(
|
||||||
|
appId = "appId",
|
||||||
|
encryptedUserKey = "encryptedUserKey",
|
||||||
|
encryptedDevicePublicKey = "encryptedDevicePublicKey",
|
||||||
|
encryptedDevicePrivateKey = "encryptedDevicePrivateKey",
|
||||||
|
)
|
||||||
|
assertEquals(TRUST_DEVICE_RESPONSE.asSuccess(), actual)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val TRUST_DEVICE_RESPONSE: TrustedDeviceKeysResponseJson =
|
||||||
|
TrustedDeviceKeysResponseJson(
|
||||||
|
id = "0d31b6fb-d282-43c7-b614-b13e0129dbd7",
|
||||||
|
name = "Pixel 8",
|
||||||
|
identifier = "ea7c0a13-5ce4-4f96-8e17-4fc7fa54f464",
|
||||||
|
type = 0,
|
||||||
|
creationDate = ZonedDateTime.parse("2024-03-25T18:04:28.23Z"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val TRUST_DEVICE_RESPONSE_JSON: String = """
|
||||||
|
{
|
||||||
|
"id":"0d31b6fb-d282-43c7-b614-b13e0129dbd7",
|
||||||
|
"name":"Pixel 8",
|
||||||
|
"type":0,
|
||||||
|
"identifier":"ea7c0a13-5ce4-4f96-8e17-4fc7fa54f464",
|
||||||
|
"creationDate":"2024-03-25T18:04:28.23Z",
|
||||||
|
"isTrusted":true,
|
||||||
|
"object":"device"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
Loading…
Reference in a new issue