mirror of
https://github.com/bitwarden/android.git
synced 2026-06-13 03:03:51 -05:00
PM-38998: Feat: Apply optional auth token to config endpoint (#7055)
This commit is contained in:
@@ -6,8 +6,12 @@ import com.bitwarden.network.BitwardenServiceClient
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
private const val ENVIRONMENT_DEBOUNCE_TIMEOUT_MS: Long = 500L
|
||||
@@ -26,9 +30,11 @@ class NetworkConfigManagerImpl(
|
||||
private val collectionScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
|
||||
init {
|
||||
@Suppress("OPT_IN_USAGE")
|
||||
environmentRepository
|
||||
.environmentStateFlow
|
||||
@OptIn(FlowPreview::class)
|
||||
combine(
|
||||
environmentRepository.environmentStateFlow,
|
||||
authRepository.userStateFlow.map { it?.activeUserId }.distinctUntilChanged(),
|
||||
) { _, _ -> }
|
||||
.debounce(timeoutMillis = ENVIRONMENT_DEBOUNCE_TIMEOUT_MS)
|
||||
.onEach { _ ->
|
||||
// This updates the stored service configuration by performing a network request.
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.bitwarden.data.repository.model.Environment
|
||||
import com.bitwarden.network.BitwardenServiceClient
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
@@ -29,10 +30,12 @@ class NetworkConfigManagerTest {
|
||||
unconfined = testDispatcher,
|
||||
)
|
||||
private val mutableAuthStateFlow = MutableStateFlow<AuthState>(AuthState.Uninitialized)
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val mutableEnvironmentStateFlow = MutableStateFlow<Environment>(Environment.Us)
|
||||
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
every { authStateFlow } returns mutableAuthStateFlow
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
private val environmentRepository: EnvironmentRepository = mockk {
|
||||
every { environmentStateFlow } returns mutableEnvironmentStateFlow
|
||||
@@ -57,10 +60,13 @@ class NetworkConfigManagerTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `changes in the Environment should call getServerConfig after debounce period`() {
|
||||
mutableEnvironmentStateFlow.value = Environment.Us
|
||||
fun `changes in the Environment or active user ID should call getServerConfig after debounce period`() {
|
||||
mutableEnvironmentStateFlow.value = Environment.Eu
|
||||
mutableUserStateFlow.value = mockk { every { activeUserId } returns "userId" }
|
||||
mutableEnvironmentStateFlow.value = Environment.Us
|
||||
mutableUserStateFlow.value = null
|
||||
testDispatcher.advanceTimeByAndRunCurrent(delayTimeMillis = 500L)
|
||||
coVerify(exactly = 1) {
|
||||
serverConfigRepository.getServerConfig(forceRefresh = true)
|
||||
|
||||
@@ -121,7 +121,7 @@ internal class BitwardenServiceClientImpl(
|
||||
|
||||
override val configService: ConfigService by lazy {
|
||||
ConfigServiceImpl(
|
||||
configApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
configApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
private const val MISSING_TOKEN_MESSAGE: String = "Auth token is missing!"
|
||||
private const val MISSING_PROVIDER_MESSAGE: String = "Refresh token provider is missing!"
|
||||
private const val EXPIRATION_OFFSET_MINUTES: Long = 5L
|
||||
|
||||
@@ -83,8 +82,10 @@ internal class AuthTokenManager(
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val token = getAccessToken()
|
||||
?: throw IOException(IllegalStateException(MISSING_TOKEN_MESSAGE))
|
||||
val token = getAccessToken() ?: run {
|
||||
Timber.w("Auth token is missing! Proceeding without token.")
|
||||
return chain.proceed(chain.request())
|
||||
}
|
||||
val request = chain
|
||||
.request()
|
||||
.newBuilder()
|
||||
|
||||
@@ -270,16 +270,19 @@ class AuthTokenManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `intercept should throw an exception when an auth token data is missing`() {
|
||||
val throwable = assertThrows(IOException::class.java) {
|
||||
authTokenManager.intercept(
|
||||
chain = FakeInterceptorChain(request = request),
|
||||
)
|
||||
fun `intercept should proceed without token when an auth token data is missing`() {
|
||||
val token = "token"
|
||||
authTokenManager.refreshTokenProvider = object : RefreshTokenProvider {
|
||||
override fun refreshAccessTokenSynchronously(
|
||||
userId: String,
|
||||
): Result<String> = token.asSuccess()
|
||||
}
|
||||
assertEquals(
|
||||
"Auth token is missing!",
|
||||
throwable.cause?.message,
|
||||
every { mockAuthTokenProvider.getAuthTokenDataOrNull() } returns null
|
||||
|
||||
val response = authTokenManager.intercept(
|
||||
chain = FakeInterceptorChain(request = request),
|
||||
)
|
||||
assertNull(response.request.header("Authorization"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user