[PM-20304] Migrate HeadersInterceptor to network module (#5061)

This commit is contained in:
Patrick Honkonen 2025-04-16 11:09:00 -04:00 committed by GitHub
parent 2d416eade5
commit 9dd71eaea2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 91 additions and 229 deletions

View File

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.di
import com.bitwarden.network.interceptor.HeadersInterceptor
import com.bitwarden.network.service.ConfigService
import com.bitwarden.network.service.ConfigServiceImpl
import com.bitwarden.network.service.EventService
@ -10,11 +11,13 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.RetrofitsImpl
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManager
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManagerImpl
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_NAME
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_VERSION
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_USER_AGENT
import com.x8bit.bitwarden.data.platform.manager.KeyManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import dagger.Module
@ -65,7 +68,11 @@ object PlatformNetworkModule {
@Provides
@Singleton
fun providesHeadersInterceptor(): HeadersInterceptor = HeadersInterceptor()
fun providesHeadersInterceptor(): HeadersInterceptor = HeadersInterceptor(
userAgent = HEADER_VALUE_USER_AGENT,
clientName = HEADER_VALUE_CLIENT_NAME,
clientVersion = HEADER_VALUE_CLIENT_VERSION,
)
@Provides
@Singleton

View File

@ -1,27 +0,0 @@
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_CLIENT_NAME
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_CLIENT_VERSION
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_DEVICE_TYPE
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_USER_AGENT
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_NAME
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_VERSION
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_DEVICE_TYPE
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_USER_AGENT
import okhttp3.Interceptor
import okhttp3.Response
/**
* Interceptor responsible for adding various headers to all API requests.
*/
class HeadersInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(
chain.request()
.newBuilder()
.header(HEADER_KEY_USER_AGENT, HEADER_VALUE_USER_AGENT)
.header(HEADER_KEY_CLIENT_NAME, HEADER_VALUE_CLIENT_NAME)
.header(HEADER_KEY_CLIENT_VERSION, HEADER_VALUE_CLIENT_VERSION)
.header(HEADER_KEY_DEVICE_TYPE, HEADER_VALUE_DEVICE_TYPE)
.build(),
)
}

View File

@ -6,7 +6,7 @@ import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.Refres
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.bitwarden.network.interceptor.HeadersInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManager
import com.x8bit.bitwarden.data.platform.util.isDevBuild
import kotlinx.serialization.json.Json

View File

@ -3,11 +3,6 @@ package com.x8bit.bitwarden.data.platform.datasource.network.util
import android.os.Build
import com.x8bit.bitwarden.BuildConfig
/**
* The key used for the 'user-agent' headers.
*/
const val HEADER_KEY_USER_AGENT: String = "User-Agent"
/**
* The value used for the 'user-agent' headers.
*/
@ -15,32 +10,12 @@ const val HEADER_KEY_USER_AGENT: String = "User-Agent"
val HEADER_VALUE_USER_AGENT: String =
"Bitwarden_Mobile/${BuildConfig.VERSION_NAME} (${BuildConfig.BUILD_TYPE}/${BuildConfig.FLAVOR}) (Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT}; Model ${Build.MODEL})"
/**
* The key used for the 'bitwarden-client-name' headers.
*/
const val HEADER_KEY_CLIENT_NAME: String = "Bitwarden-Client-Name"
/**
* The value used for the 'bitwarden-client-name' headers.
*/
const val HEADER_VALUE_CLIENT_NAME: String = "mobile"
/**
* The key used for the 'bitwarden-client-version' headers.
*/
const val HEADER_KEY_CLIENT_VERSION: String = "Bitwarden-Client-Version"
/**
* The value used for the 'bitwarden-client-version' headers.
*/
const val HEADER_VALUE_CLIENT_VERSION: String = BuildConfig.VERSION_NAME
/**
* The key used for the 'device-type' headers.
*/
const val HEADER_KEY_DEVICE_TYPE: String = "Device-Type"
/**
* The value used for the 'device-type' headers.
*/
const val HEADER_VALUE_DEVICE_TYPE: String = "0"

View File

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
import com.bitwarden.network.interceptor.FakeInterceptorChain
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource

View File

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
import com.bitwarden.network.interceptor.FakeInterceptorChain
import okhttp3.Request
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals

View File

@ -4,7 +4,7 @@ import com.bitwarden.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.bitwarden.network.interceptor.HeadersInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.ssl.SslManager
import com.x8bit.bitwarden.data.util.mockBuilder
import io.mockk.every

View File

@ -1,9 +1,12 @@
package com.bitwarden.authenticator.data.platform.datasource.network.di
import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.bitwarden.authenticator.data.platform.datasource.network.retrofit.Retrofits
import com.bitwarden.authenticator.data.platform.datasource.network.retrofit.RetrofitsImpl
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_NAME
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_VERSION
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_VALUE_USER_AGENT
import com.bitwarden.network.interceptor.HeadersInterceptor
import com.bitwarden.network.service.ConfigService
import com.bitwarden.network.service.ConfigServiceImpl
import dagger.Module
@ -29,7 +32,11 @@ object PlatformNetworkModule {
@Provides
@Singleton
fun providesHeadersInterceptor(): HeadersInterceptor = HeadersInterceptor()
fun providesHeadersInterceptor(): HeadersInterceptor = HeadersInterceptor(
userAgent = HEADER_VALUE_USER_AGENT,
clientName = HEADER_VALUE_CLIENT_NAME,
clientVersion = HEADER_VALUE_CLIENT_VERSION,
)
@Provides
@Singleton

View File

@ -1,27 +0,0 @@
package com.bitwarden.authenticator.data.platform.datasource.network.interceptor
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_KEY_CLIENT_NAME
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_KEY_CLIENT_VERSION
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_KEY_DEVICE_TYPE
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_KEY_USER_AGENT
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_NAME
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_VERSION
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_VALUE_DEVICE_TYPE
import com.bitwarden.authenticator.data.platform.datasource.network.util.HEADER_VALUE_USER_AGENT
import okhttp3.Interceptor
import okhttp3.Response
/**
* Interceptor responsible for adding various headers to all API requests.
*/
class HeadersInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(
chain.request()
.newBuilder()
.header(HEADER_KEY_USER_AGENT, HEADER_VALUE_USER_AGENT)
.header(HEADER_KEY_CLIENT_NAME, HEADER_VALUE_CLIENT_NAME)
.header(HEADER_KEY_CLIENT_VERSION, HEADER_VALUE_CLIENT_VERSION)
.header(HEADER_KEY_DEVICE_TYPE, HEADER_VALUE_DEVICE_TYPE)
.build(),
)
}

View File

@ -2,7 +2,7 @@ package com.bitwarden.authenticator.data.platform.datasource.network.retrofit
import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.BaseUrlInterceptor
import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.bitwarden.network.interceptor.HeadersInterceptor
import com.bitwarden.network.core.NetworkResultCallAdapterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType

View File

@ -3,11 +3,6 @@ package com.bitwarden.authenticator.data.platform.datasource.network.util
import android.os.Build
import com.bitwarden.authenticator.BuildConfig
/**
* The key used for the 'user-agent' headers.
*/
const val HEADER_KEY_USER_AGENT: String = "User-Agent"
/**
* The value used for the 'user-agent' headers.
*/
@ -15,32 +10,12 @@ const val HEADER_KEY_USER_AGENT: String = "User-Agent"
val HEADER_VALUE_USER_AGENT: String =
"Bitwarden_Mobile/${BuildConfig.VERSION_NAME} (${BuildConfig.BUILD_TYPE}) (Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT}; Model ${Build.MODEL})"
/**
* The key used for the 'bitwarden-client-name' headers.
*/
const val HEADER_KEY_CLIENT_NAME: String = "Bitwarden-Client-Name"
/**
* The value used for the 'bitwarden-client-name' headers.
*/
const val HEADER_VALUE_CLIENT_NAME: String = "mobile"
/**
* The key used for the 'bitwarden-client-version' headers.
*/
const val HEADER_KEY_CLIENT_VERSION: String = "Bitwarden-Client-Version"
/**
* The value used for the 'bitwarden-client-version' headers.
*/
const val HEADER_VALUE_CLIENT_VERSION: String = BuildConfig.VERSION_NAME
/**
* The key used for the 'device-type' headers.
*/
const val HEADER_KEY_DEVICE_TYPE: String = "Device-Type"
/**
* The value used for the 'device-type' headers.
*/
const val HEADER_VALUE_DEVICE_TYPE: String = "0"

View File

@ -1,5 +1,6 @@
package com.bitwarden.authenticator.data.platform.datasource.network.interceptor
import com.bitwarden.network.interceptor.FakeInterceptorChain
import okhttp3.Request
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals

View File

@ -1,69 +0,0 @@
package com.bitwarden.authenticator.data.platform.datasource.network.interceptor
import okhttp3.Call
import okhttp3.Connection
import okhttp3.Interceptor
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import java.util.concurrent.TimeUnit
/**
* Helper class for implementing a [Interceptor.Chain] in a way that a [Request] passed in to
* [proceed] will be returned in a valid [Response] object that can be queried. This wrapping is
* performed by the [responseProvider].
*/
class FakeInterceptorChain(
private val request: Request,
private val responseProvider: (Request) -> Response = DEFAULT_RESPONSE_PROVIDER,
) : Interceptor.Chain {
override fun request(): Request = request
override fun proceed(request: Request): Response = responseProvider(request)
override fun connection(): Connection = notImplemented()
override fun call(): Call = notImplemented()
override fun connectTimeoutMillis(): Int = notImplemented()
override fun withConnectTimeout(
timeout: Int,
unit: TimeUnit,
): Interceptor.Chain = notImplemented()
override fun readTimeoutMillis(): Int = notImplemented()
override fun withReadTimeout(
timeout: Int,
unit: TimeUnit,
): Interceptor.Chain = notImplemented()
override fun writeTimeoutMillis(): Int = notImplemented()
override fun withWriteTimeout(
timeout: Int,
unit: TimeUnit,
): Interceptor.Chain = notImplemented()
private fun notImplemented(): Nothing {
throw NotImplementedError("This is not yet required by tests")
}
companion object {
/**
* A default response provider that provides a basic successful response. This is useful
* when the details of the response are not as important as retrieving the [Request] that
* was used to build it.
*/
val DEFAULT_RESPONSE_PROVIDER: (Request) -> Response = { request ->
Response
.Builder()
.code(200)
.message("OK")
.protocol(Protocol.HTTP_1_1)
.request(request)
.build()
}
}
}

View File

@ -1,37 +0,0 @@
package com.bitwarden.authenticator.data.platform.datasource.network.interceptor
import android.os.Build
import com.bitwarden.authenticator.BuildConfig
import com.bitwarden.authenticator.ui.platform.base.BaseRobolectricTest
import okhttp3.Request
import org.junit.Assert.assertEquals
import org.junit.Test
class HeadersInterceptorTest : BaseRobolectricTest() {
private val headersInterceptors = HeadersInterceptor()
@Test
fun `intercept should modify original request to include custom headers`() {
// We reference the real BuildConfig here, since we don't want the test to break on every
// version bump. We are also doing the same thing for Build when the SDK gets incremented.
val versionName = BuildConfig.VERSION_NAME
val buildType = BuildConfig.BUILD_TYPE
val release = Build.VERSION.RELEASE
val sdk = Build.VERSION.SDK_INT
val originalRequest = Request.Builder().url("http://www.fake.com/").build()
val chain = FakeInterceptorChain(originalRequest)
val response = headersInterceptors.intercept(chain)
val request = response.request
@Suppress("MaxLineLength")
assertEquals(
"Bitwarden_Mobile/$versionName ($buildType) (Android $release; SDK $sdk; Model robolectric)",
request.header("User-Agent"),
)
assertEquals("mobile", request.header("Bitwarden-Client-Name"))
assertEquals(versionName, request.header("Bitwarden-Client-Version"))
assertEquals("0", request.header("Device-Type"))
}
}

View File

@ -1,7 +1,7 @@
package com.bitwarden.authenticator.data.platform.datasource.network.retrofit
import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.bitwarden.network.interceptor.HeadersInterceptor
import com.bitwarden.network.model.NetworkResult
import io.mockk.every
import io.mockk.mockk

View File

@ -0,0 +1,28 @@
package com.bitwarden.network.interceptor
import com.bitwarden.network.util.HEADER_KEY_CLIENT_NAME
import com.bitwarden.network.util.HEADER_KEY_CLIENT_VERSION
import com.bitwarden.network.util.HEADER_KEY_DEVICE_TYPE
import com.bitwarden.network.util.HEADER_KEY_USER_AGENT
import com.bitwarden.network.util.HEADER_VALUE_DEVICE_TYPE
import okhttp3.Interceptor
import okhttp3.Response
/**
* Interceptor responsible for adding various headers to all API requests.
*/
class HeadersInterceptor(
private val userAgent: String,
private val clientName: String,
private val clientVersion: String,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(
chain.request()
.newBuilder()
.header(HEADER_KEY_USER_AGENT, userAgent)
.header(HEADER_KEY_CLIENT_NAME, clientName)
.header(HEADER_KEY_CLIENT_VERSION, clientVersion)
.header(HEADER_KEY_DEVICE_TYPE, HEADER_VALUE_DEVICE_TYPE)
.build(),
)
}

View File

@ -5,7 +5,32 @@ package com.bitwarden.network.util
*/
const val HEADER_KEY_AUTHORIZATION: String = "Authorization"
/**
* The key used for the 'bitwarden-client-name' headers.
*/
internal const val HEADER_KEY_CLIENT_NAME: String = "Bitwarden-Client-Name"
/**
* The key used for the 'bitwarden-client-version' headers.
*/
internal const val HEADER_KEY_CLIENT_VERSION: String = "Bitwarden-Client-Version"
/**
* The key used for the 'user-agent' headers.
*/
internal const val HEADER_KEY_USER_AGENT: String = "User-Agent"
/**
* The key used for the 'device-type' headers.
*/
internal const val HEADER_KEY_DEVICE_TYPE: String = "Device-Type"
/**
* The bearer prefix used for the 'authorization' headers value.
*/
const val HEADER_VALUE_BEARER_PREFIX: String = "Bearer "
/**
* The value used for the 'device-type' headers.
*/
internal const val HEADER_VALUE_DEVICE_TYPE: String = "0"

View File

@ -1,28 +1,30 @@
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
package com.bitwarden.network.interceptor
import android.os.Build
import com.x8bit.bitwarden.BuildConfig
import com.x8bit.bitwarden.ui.platform.base.BaseRobolectricTest
import okhttp3.Request
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
class HeadersInterceptorTest : BaseRobolectricTest() {
private val headersInterceptors = HeadersInterceptor()
class HeadersInterceptorTest {
@Test
fun `intercept should modify original request to include custom headers`() {
// We reference the real BuildConfig here, since we don't want the test to break on every
// version bump. We are also doing the same thing for Build when the SDK gets incremented.
val versionName = BuildConfig.VERSION_NAME
val buildType = BuildConfig.BUILD_TYPE
val flavor = BuildConfig.FLAVOR
val versionName = "VersionName"
val buildType = "BuildType"
val flavor = "Flavor"
val release = Build.VERSION.RELEASE
val sdk = Build.VERSION.SDK_INT
val originalRequest = Request.Builder().url("http://www.fake.com/").build()
val chain = FakeInterceptorChain(originalRequest)
@Suppress("MaxLineLength")
val headersInterceptors = HeadersInterceptor(
userAgent = "Bitwarden_Mobile/$versionName ($buildType/$flavor) (Android $release; SDK $sdk; Model robolectric)",
clientName = "mobile",
clientVersion = versionName,
)
val response = headersInterceptors.intercept(chain)
val request = response.request

View File

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
package com.bitwarden.network.interceptor
import okhttp3.Call
import okhttp3.Connection