mirror of
https://github.com/bitwarden/android.git
synced 2026-03-11 20:54:58 -05:00
Redacting hostname when it matches a self-hosted hostname
Replacing api url with selfhosted one on toFailure at NetworkResultCall
This commit is contained in:
@@ -17,6 +17,7 @@ import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManagerImpl
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderWriter
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderWriterImpl
|
||||
import com.bitwarden.network.interceptor.BaseUrlsProvider
|
||||
import com.bitwarden.network.service.DownloadService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@@ -80,11 +81,13 @@ object DataManagerModule {
|
||||
fileManager: FileManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
buildInfoManager: BuildInfoManager,
|
||||
baseUrlsProvider: BaseUrlsProvider,
|
||||
): FlightRecorderWriter = FlightRecorderWriterImpl(
|
||||
clock = clock,
|
||||
fileManager = fileManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
buildInfoManager = buildInfoManager,
|
||||
baseUrlsProvider = baseUrlsProvider,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -8,7 +8,10 @@ import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.core.data.util.toFormattedPattern
|
||||
import com.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.bitwarden.data.manager.file.FileManager
|
||||
import com.bitwarden.network.interceptor.BaseUrlsProvider
|
||||
import com.bitwarden.network.util.redactHostnamesInMessage
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import timber.log.Timber
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
@@ -30,6 +33,7 @@ internal class FlightRecorderWriterImpl(
|
||||
private val fileManager: FileManager,
|
||||
private val dispatcherManager: DispatcherManager,
|
||||
private val buildInfoManager: BuildInfoManager,
|
||||
private val baseUrlsProvider: BaseUrlsProvider,
|
||||
) : FlightRecorderWriter {
|
||||
override suspend fun deleteLog(data: FlightRecorderDataSet.FlightRecorderData) {
|
||||
fileManager.delete(File(File(fileManager.logsDirectory), data.fileName))
|
||||
@@ -103,16 +107,39 @@ internal class FlightRecorderWriterImpl(
|
||||
bw.append(it)
|
||||
}
|
||||
bw.append(" – ")
|
||||
bw.append(message.redactUrls())
|
||||
bw.append(message.redactUrls()) // Apply hostname redaction
|
||||
throwable?.let {
|
||||
bw.append(" – ")
|
||||
bw.append(it.getStackTraceString().redactUrls())
|
||||
bw.append(it.getStackTraceString().redactUrls()) // Also redact stack traces
|
||||
}
|
||||
bw.newLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redacts ONLY the user's configured self-hosted server hostname.
|
||||
*
|
||||
* Preserves ALL Bitwarden domains (including QA/staging).
|
||||
* Delegates to [com.bitwarden.network.util.redactHostnamesInMessage].
|
||||
*
|
||||
* Examples:
|
||||
* - "https://api.bitwarden.com/sync" → unchanged (Bitwarden cloud)
|
||||
* - "https://vault.qa.bitwarden.pw/api" → unchanged (Bitwarden QA)
|
||||
* - "https://vault.example.com/api" → "https://[REDACTED_SELF_HOST]/api" (self-hosted)
|
||||
*/
|
||||
private fun String.redactUrls(): String {
|
||||
// Get configured hostnames from BaseUrlsProvider
|
||||
val configuredHosts = setOf(
|
||||
baseUrlsProvider.getBaseApiUrl().toHttpUrlOrNull()?.host,
|
||||
baseUrlsProvider.getBaseIdentityUrl().toHttpUrlOrNull()?.host,
|
||||
baseUrlsProvider.getBaseEventsUrl().toHttpUrlOrNull()?.host,
|
||||
).filterNotNull().toSet()
|
||||
|
||||
// Delegate to HostnameRedactionUtil for all redaction logic
|
||||
return this.redactHostnamesInMessage(configuredHosts)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,24 +168,3 @@ private val Int.logLevel: String
|
||||
Log.ASSERT -> "ASSERT"
|
||||
else -> "UNKNOWN"
|
||||
}
|
||||
|
||||
/**
|
||||
* Redacts URLs and quoted hostnames in the string by replacing them with [REDACTED].
|
||||
* Handles both full URLs and hostnames in quotes (e.g., "Unable to resolve host "example.com"").
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
private fun String.redactUrls(): String {
|
||||
val urlPattern = Regex("""(https?://)([\w.-]+)((?:/[\w./?&=%-]*)?)""")
|
||||
val afterUrlRedaction = urlPattern.replace(this) { matchResult ->
|
||||
val protocol = matchResult.groupValues[1]
|
||||
val path = matchResult.groupValues[3]
|
||||
"$protocol[REDACTED]$path"
|
||||
}
|
||||
|
||||
// Redact hostnames that appear in double quotes without protocol and path
|
||||
// This handles cases like: Unable to resolve host "com.example.server"
|
||||
val quotedHostnamePattern = Regex(""""([\w-]+\.[\w.-]+)"""")
|
||||
return quotedHostnamePattern.replace(afterUrlRedaction) {
|
||||
""""[REDACTED]""""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.bitwarden.network.core
|
||||
|
||||
import com.bitwarden.network.interceptor.BaseUrlsProvider
|
||||
import com.bitwarden.network.model.NetworkResult
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Request
|
||||
import okio.IOException
|
||||
import okio.Timeout
|
||||
@@ -23,10 +25,12 @@ private const val NO_CONTENT_RESPONSE_CODE: Int = 204
|
||||
internal class NetworkResultCall<T>(
|
||||
private val backingCall: Call<T>,
|
||||
private val successType: Type,
|
||||
private val baseUrlsProvider: BaseUrlsProvider? = null,
|
||||
) : Call<NetworkResult<T>> {
|
||||
override fun cancel(): Unit = backingCall.cancel()
|
||||
|
||||
override fun clone(): Call<NetworkResult<T>> = NetworkResultCall(backingCall, successType)
|
||||
override fun clone(): Call<NetworkResult<T>> =
|
||||
NetworkResultCall(backingCall, successType, baseUrlsProvider)
|
||||
|
||||
override fun enqueue(callback: Callback<NetworkResult<T>>): Unit = backingCall.enqueue(
|
||||
object : Callback<T> {
|
||||
@@ -67,8 +71,32 @@ internal class NetworkResultCall<T>(
|
||||
fun executeForResult(): NetworkResult<T> = requireNotNull(execute().body())
|
||||
|
||||
private fun Throwable.toFailure(): NetworkResult<T> {
|
||||
// We rebuild the URL without query params, we do not want to log those
|
||||
val url = backingCall.request().url.toUrl().run { "$protocol://$authority$path" }
|
||||
val originalUrl = backingCall.request().url.toUrl()
|
||||
|
||||
// Check if this is a hardcoded default URL that will be replaced by BaseUrlInterceptor
|
||||
// Match against the defaults from RetrofitsImpl.kt line 111 and EnvironmentUrlDataJson
|
||||
val actualHost = if (baseUrlsProvider != null) {
|
||||
when (originalUrl.host) {
|
||||
"api.bitwarden.com" -> baseUrlsProvider.getBaseApiUrl().toHttpUrlOrNull()?.host
|
||||
"identity.bitwarden.com" -> baseUrlsProvider.getBaseIdentityUrl()
|
||||
.toHttpUrlOrNull()?.host
|
||||
|
||||
"events.bitwarden.com" -> baseUrlsProvider.getBaseEventsUrl()
|
||||
.toHttpUrlOrNull()?.host
|
||||
|
||||
else -> null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Rebuild the URL without query params, using actual host if available
|
||||
val url = if (actualHost != null) {
|
||||
"${originalUrl.protocol}://$actualHost${originalUrl.path}"
|
||||
} else {
|
||||
"${originalUrl.protocol}://${originalUrl.authority}${originalUrl.path}"
|
||||
}
|
||||
|
||||
Timber.w(this, "Network Error: $url")
|
||||
return NetworkResult.Failure(this)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bitwarden.network.core
|
||||
|
||||
import com.bitwarden.network.interceptor.BaseUrlsProvider
|
||||
import com.bitwarden.network.model.NetworkResult
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
@@ -10,8 +11,10 @@ import java.lang.reflect.Type
|
||||
*/
|
||||
internal class NetworkResultCallAdapter<T>(
|
||||
private val successType: Type,
|
||||
private val baseUrlsProvider: BaseUrlsProvider,
|
||||
) : CallAdapter<T, Call<NetworkResult<T>>> {
|
||||
|
||||
override fun responseType(): Type = successType
|
||||
override fun adapt(call: Call<T>): Call<NetworkResult<T>> = NetworkResultCall(call, successType)
|
||||
override fun adapt(call: Call<T>): Call<NetworkResult<T>> =
|
||||
NetworkResultCall(call, successType, baseUrlsProvider)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bitwarden.network.core
|
||||
|
||||
import com.bitwarden.network.interceptor.BaseUrlsProvider
|
||||
import com.bitwarden.network.model.NetworkResult
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
@@ -10,7 +11,9 @@ import java.lang.reflect.Type
|
||||
/**
|
||||
* A [retrofit2.CallAdapter.Factory] for wrapping network requests into [NetworkResult].
|
||||
*/
|
||||
internal class NetworkResultCallAdapterFactory : CallAdapter.Factory() {
|
||||
internal class NetworkResultCallAdapterFactory(
|
||||
private val baseUrlsProvider: BaseUrlsProvider,
|
||||
) : CallAdapter.Factory() {
|
||||
override fun get(
|
||||
returnType: Type,
|
||||
annotations: Array<out Annotation>,
|
||||
@@ -25,7 +28,10 @@ internal class NetworkResultCallAdapterFactory : CallAdapter.Factory() {
|
||||
val requestType = getParameterUpperBound(0, containerType)
|
||||
|
||||
return if (getRawType(returnType) == Call::class.java) {
|
||||
NetworkResultCallAdapter<Any>(successType = requestType)
|
||||
NetworkResultCallAdapter<Any>(
|
||||
successType = requestType,
|
||||
baseUrlsProvider = baseUrlsProvider,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.bitwarden.annotation.OmitFromCoverage
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
internal class BaseUrlInterceptors(
|
||||
private val baseUrlsProvider: BaseUrlsProvider,
|
||||
val baseUrlsProvider: BaseUrlsProvider,
|
||||
) {
|
||||
/**
|
||||
* An interceptor for "/api" calls.
|
||||
|
||||
@@ -23,7 +23,7 @@ import timber.log.Timber
|
||||
@Suppress("LongParameterList")
|
||||
internal class RetrofitsImpl(
|
||||
authTokenManager: AuthTokenManager,
|
||||
baseUrlInterceptors: BaseUrlInterceptors,
|
||||
private val baseUrlInterceptors: BaseUrlInterceptors,
|
||||
cookieInterceptor: CookieInterceptor,
|
||||
headersInterceptor: HeadersInterceptor,
|
||||
json: Json,
|
||||
@@ -115,7 +115,9 @@ internal class RetrofitsImpl(
|
||||
private val baseRetrofitBuilder: Retrofit.Builder by lazy {
|
||||
Retrofit.Builder()
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.addCallAdapterFactory(NetworkResultCallAdapterFactory())
|
||||
.addCallAdapterFactory(
|
||||
NetworkResultCallAdapterFactory(baseUrlInterceptors.baseUrlsProvider),
|
||||
)
|
||||
.client(baseOkHttpClient)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.bitwarden.network.util
|
||||
|
||||
/**
|
||||
* List of official Bitwarden cloud hostnames that are safe to log.
|
||||
*/
|
||||
private val BITWARDEN_HOSTS = listOf("bitwarden.com", "bitwarden.eu", "bitwarden.pw")
|
||||
|
||||
/**
|
||||
* Redacts hostnames in a log message by replacing bare hostnames with [REDACTED_SELF_HOST].
|
||||
*
|
||||
* Only redacts hostnames that match [configuredHosts] AND are not official Bitwarden domains.
|
||||
* Preserves all Bitwarden domains (including QA/dev environments).
|
||||
*
|
||||
* @param configuredHosts Set of hostnames from BaseUrlsProvider
|
||||
* @return Message with hostnames redacted as [REDACTED_SELF_HOST]
|
||||
*/
|
||||
fun String.redactHostnamesInMessage(configuredHosts: Set<String>): String =
|
||||
configuredHosts.fold(this) { result, hostname ->
|
||||
val escapedHostname = Regex.escape(hostname)
|
||||
val bareHostnamePattern = Regex("""\b$escapedHostname\b""")
|
||||
bareHostnamePattern.replace(result) { hostname.redactIfSelfHosted() }
|
||||
}
|
||||
|
||||
private fun String.redactIfSelfHosted(): String {
|
||||
val isBitwardenHost = BITWARDEN_HOSTS.any { this.endsWith(it) }
|
||||
return if (isBitwardenHost) this else "[REDACTED_SELF_HOST]"
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.bitwarden.network.core
|
||||
|
||||
import com.bitwarden.network.interceptor.BaseUrlsProvider
|
||||
import com.bitwarden.network.model.NetworkResult
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
@@ -13,12 +16,18 @@ import retrofit2.http.GET
|
||||
|
||||
class NetworkResultCallAdapterTest {
|
||||
|
||||
private val mockBaseUrlsProvider = mockk<BaseUrlsProvider> {
|
||||
every { getBaseApiUrl() } returns "https://api.bitwarden.com"
|
||||
every { getBaseIdentityUrl() } returns "https://identity.bitwarden.com"
|
||||
every { getBaseEventsUrl() } returns "https://events.bitwarden.com"
|
||||
}
|
||||
|
||||
private val server: MockWebServer = MockWebServer().apply { start() }
|
||||
private val testService: FakeService =
|
||||
Retrofit.Builder()
|
||||
.baseUrl(server.url("/").toString())
|
||||
// add the adapter being tested
|
||||
.addCallAdapterFactory(NetworkResultCallAdapterFactory())
|
||||
.addCallAdapterFactory(NetworkResultCallAdapterFactory(mockBaseUrlsProvider))
|
||||
.build()
|
||||
.create()
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class RetrofitsTest {
|
||||
mockIntercept { isAuthInterceptorCalled = true }
|
||||
}
|
||||
private val baseUrlInterceptors = mockk<BaseUrlInterceptors> {
|
||||
every { baseUrlsProvider } returns mockk(relaxed = true)
|
||||
every { apiInterceptor } returns mockk {
|
||||
mockIntercept { isApiInterceptorCalled = true }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.bitwarden.network.util
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class HostnameRedactionUtilTest {
|
||||
@Test
|
||||
fun `redactHostnamesInMessage redacts configured self-hosted URLs`() {
|
||||
val message = "--> GET https://vault.example.com/api/sync HTTP/1.1"
|
||||
val configuredHosts = setOf("vault.example.com")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals("--> GET https://[REDACTED_SELF_HOST]/api/sync HTTP/1.1", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage preserves non-configured URLs`() {
|
||||
val message = "--> GET https://vault.example.com/api/sync HTTP/1.1"
|
||||
val configuredHosts = setOf("api.bitwarden.com") // Different host
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(message, result) // Unchanged - not in configured hosts
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage preserves Bitwarden URLs even if configured`() {
|
||||
val message = "--> GET https://vault.qa.bitwarden.pw/api/sync HTTP/1.1"
|
||||
val configuredHosts = setOf("vault.qa.bitwarden.pw")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(message, result) // Unchanged - Bitwarden domain preserved
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage redacts quoted hostnames in error messages`() {
|
||||
val message = """Unable to resolve host "vault.example.com": No address"""
|
||||
val configuredHosts = setOf("vault.example.com")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals("""Unable to resolve host "[REDACTED_SELF_HOST]": No address""", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage handles multiple URLs in one message`() {
|
||||
val message = "Redirect from https://old.corp.com to https://new.corp.com"
|
||||
val configuredHosts = setOf("old.corp.com", "new.corp.com")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(
|
||||
"Redirect from https://[REDACTED_SELF_HOST] to https://[REDACTED_SELF_HOST]",
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage handles empty configured hosts`() {
|
||||
val message = "--> GET https://vault.example.com/api HTTP/1.1"
|
||||
val configuredHosts = emptySet<String>()
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(message, result) // Unchanged - no hosts to redact
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage handles NetworkCookieManagerImpl getCookies pattern`() {
|
||||
val message = "2026-03-09 12:43:29:857 – DEBUG – NetworkCookieManagerImpl – " +
|
||||
"getCookies(vault.example.com): resolved=vault.example.com, count=0"
|
||||
val configuredHosts = setOf("vault.example.com")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(
|
||||
"2026-03-09 12:43:29:857 – DEBUG – NetworkCookieManagerImpl – " +
|
||||
"getCookies([REDACTED_SELF_HOST]): resolved=[REDACTED_SELF_HOST], count=0",
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage preserves Bitwarden domains in NetworkCookieManagerImpl logs`() {
|
||||
val message = "2026-03-09 12:43:29:857 – DEBUG – NetworkCookieManagerImpl – " +
|
||||
"getCookies(vault.example.com): resolved=vault.qa.bitwarden.pw, count=0"
|
||||
val configuredHosts = setOf("vault.example.com", "vault.qa.bitwarden.pw")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(
|
||||
"2026-03-09 12:43:29:857 – DEBUG – NetworkCookieManagerImpl – " +
|
||||
"getCookies([REDACTED_SELF_HOST]): resolved=vault.qa.bitwarden.pw, count=0",
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage handles UnknownHostException error message`() {
|
||||
val message = "DEBUG – BitwardenNetworkClient – <-- HTTP FAILED: " +
|
||||
"java.net.UnknownHostException: Unable to resolve host " +
|
||||
"\"vault.example.com\": No address associated with hostname."
|
||||
val configuredHosts = setOf("vault.example.com")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(
|
||||
"DEBUG – BitwardenNetworkClient – <-- HTTP FAILED: " +
|
||||
"java.net.UnknownHostException: Unable to resolve host " +
|
||||
"\"[REDACTED_SELF_HOST]\": No address associated with hostname.",
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage handles needsBootstrap pattern`() {
|
||||
val message = "2026-03-09 12:43:29:851 – DEBUG – NetworkCookieManagerImpl – " +
|
||||
"needsBootstrap(vault.example.com): false (cookieDomain=null)"
|
||||
val configuredHosts = setOf("vault.example.com")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(
|
||||
"2026-03-09 12:43:29:851 – DEBUG – NetworkCookieManagerImpl – " +
|
||||
"needsBootstrap([REDACTED_SELF_HOST]): false (cookieDomain=null)",
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `redactHostnamesInMessage handles resolveHostname pattern`() {
|
||||
val message = "2026-03-09 12:43:29:855 – DEBUG – NetworkCookieManagerImpl – " +
|
||||
"resolveHostname(vault.example.com): no stored config found, using original"
|
||||
val configuredHosts = setOf("vault.example.com")
|
||||
|
||||
val result = message.redactHostnamesInMessage(configuredHosts)
|
||||
|
||||
assertEquals(
|
||||
"2026-03-09 12:43:29:855 – DEBUG – NetworkCookieManagerImpl – " +
|
||||
"resolveHostname([REDACTED_SELF_HOST]): no stored config found, using original",
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.bitwarden.network.base
|
||||
|
||||
import com.bitwarden.core.di.CoreModule
|
||||
import com.bitwarden.network.core.NetworkResultCallAdapterFactory
|
||||
import com.bitwarden.network.interceptor.BaseUrlsProvider
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
@@ -22,9 +23,15 @@ abstract class BaseServiceTest {
|
||||
|
||||
protected val urlPrefix: String get() = "http://${server.hostName}:${server.port}"
|
||||
|
||||
private val fakeBaseUrlsProvider = object : BaseUrlsProvider {
|
||||
override fun getBaseApiUrl(): String = "https://api.bitwarden.com"
|
||||
override fun getBaseIdentityUrl(): String = "https://identity.bitwarden.com"
|
||||
override fun getBaseEventsUrl(): String = "https://events.bitwarden.com"
|
||||
}
|
||||
|
||||
protected val retrofit: Retrofit = Retrofit.Builder()
|
||||
.baseUrl(url.toString())
|
||||
.addCallAdapterFactory(NetworkResultCallAdapterFactory())
|
||||
.addCallAdapterFactory(NetworkResultCallAdapterFactory(fakeBaseUrlsProvider))
|
||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.build()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user