BIT-685: Add request headers to all network requests in the app (#300)

This commit is contained in:
David Perez 2023-11-30 09:49:55 -06:00 committed by Álison Fernandes
parent 798fdf3e19
commit 9fcd2b1690
8 changed files with 149 additions and 15 deletions

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.datasource.network.di
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.serializer.LocalDateTimeSerializer
@ -37,6 +38,10 @@ object PlatformNetworkModule {
@Singleton
fun providesAuthTokenInterceptor(): AuthTokenInterceptor = AuthTokenInterceptor()
@Provides
@Singleton
fun providesHeadersInterceptor(): HeadersInterceptor = HeadersInterceptor()
@Provides
@Singleton
fun providesRefreshAuthenticator(): RefreshAuthenticator = RefreshAuthenticator()
@ -46,12 +51,14 @@ object PlatformNetworkModule {
fun provideRetrofits(
authTokenInterceptor: AuthTokenInterceptor,
baseUrlInterceptors: BaseUrlInterceptors,
headersInterceptor: HeadersInterceptor,
refreshAuthenticator: RefreshAuthenticator,
json: Json,
): Retrofits =
RetrofitsImpl(
authTokenInterceptor = authTokenInterceptor,
baseUrlInterceptors = baseUrlInterceptors,
headersInterceptor = headersInterceptor,
refreshAuthenticator = refreshAuthenticator,
json = json,
)

View file

@ -0,0 +1,27 @@
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,6 +6,7 @@ import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCallAdapt
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 kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
@ -18,6 +19,7 @@ import retrofit2.Retrofit
class RetrofitsImpl(
authTokenInterceptor: AuthTokenInterceptor,
baseUrlInterceptors: BaseUrlInterceptors,
headersInterceptor: HeadersInterceptor,
refreshAuthenticator: RefreshAuthenticator,
json: Json,
) : Retrofits {
@ -70,6 +72,7 @@ class RetrofitsImpl(
private val baseOkHttpClient: OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(headersInterceptor)
.build()
private val authenticatedOkHttpClient: OkHttpClient by lazy {

View file

@ -1,5 +1,8 @@
package com.x8bit.bitwarden.data.platform.datasource.network.util
import android.os.Build
import com.x8bit.bitwarden.BuildConfig
/**
* The bearer prefix used for the 'authorization' headers value.
*/
@ -9,3 +12,45 @@ const val HEADER_VALUE_BEARER_PREFIX: String = "Bearer "
* The key used for the 'authorization' headers.
*/
const val HEADER_KEY_AUTHORIZATION: String = "Authorization"
/**
* 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.
*/
@Suppress("MaxLineLength")
val HEADER_VALUE_USER_AGENT: String =
"Bitwarden_Mobile/${BuildConfig.VERSION_NAME} (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

@ -0,0 +1,35 @@
package com.x8bit.bitwarden.data.platform.datasource.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
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 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
assertEquals(
"Bitwarden_Mobile/$versionName (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

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.datasource.network.retrofit
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 io.mockk.every
import io.mockk.mockk
import io.mockk.slot
@ -37,6 +38,9 @@ class RetrofitsTest {
mockIntercept { isEventsInterceptorCalled = true }
}
}
private val headersInterceptors = mockk<HeadersInterceptor> {
mockIntercept { isheadersInterceptorCalled = true }
}
private val refreshAuthenticator = mockk<RefreshAuthenticator> {
mockAuthenticate { isRefreshAuthenticatorCalled = true }
}
@ -46,12 +50,14 @@ class RetrofitsTest {
private val retrofits = RetrofitsImpl(
authTokenInterceptor = authTokenInterceptor,
baseUrlInterceptors = baseUrlInterceptors,
headersInterceptor = headersInterceptors,
refreshAuthenticator = refreshAuthenticator,
json = json,
)
private var isAuthInterceptorCalled = false
private var isApiInterceptorCalled = false
private var isheadersInterceptorCalled = false
private var isIdentityInterceptorCalled = false
private var isEventsInterceptorCalled = false
private var isRefreshAuthenticatorCalled = false
@ -122,6 +128,7 @@ class RetrofitsTest {
assertTrue(isAuthInterceptorCalled)
assertTrue(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled)
assertFalse(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled)
}
@ -139,6 +146,7 @@ class RetrofitsTest {
assertFalse(isAuthInterceptorCalled)
assertTrue(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled)
assertFalse(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled)
}
@ -156,6 +164,7 @@ class RetrofitsTest {
assertFalse(isAuthInterceptorCalled)
assertFalse(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled)
assertTrue(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled)
}
@ -175,6 +184,7 @@ class RetrofitsTest {
assertFalse(isAuthInterceptorCalled)
assertFalse(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled)
assertFalse(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled)
}

View file

@ -3,30 +3,16 @@ package com.x8bit.bitwarden.ui.platform.base
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.createComposeRule
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import dagger.hilt.android.testing.HiltTestApplication
import org.junit.Rule
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.shadows.ShadowLog
/**
* A base class that can be used for performing Compose-layer testing using Robolectric, Compose
* Testing, and JUnit 4.
*/
@Config(
application = HiltTestApplication::class,
sdk = [Config.NEWEST_SDK],
)
@RunWith(RobolectricTestRunner::class)
abstract class BaseComposeTest {
abstract class BaseComposeTest : BaseRobolectricTest() {
@get:Rule
val composeTestRule = createComposeRule()
init {
ShadowLog.stream = System.out
}
/**
* Helper for testing a basic Composable function that only requires a Composable environment
* with the [BitwardenTheme].

View file

@ -0,0 +1,21 @@
package com.x8bit.bitwarden.ui.platform.base
import dagger.hilt.android.testing.HiltTestApplication
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.shadows.ShadowLog
/**
* A base class that can be used for performing tests that use Robolectric and JUnit 4.
*/
@Config(
application = HiltTestApplication::class,
sdk = [Config.NEWEST_SDK],
)
@RunWith(RobolectricTestRunner::class)
abstract class BaseRobolectricTest {
init {
ShadowLog.stream = System.out
}
}