Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Closes #8492: Gracefully handle network failures while obtaining toke…
Browse files Browse the repository at this point in the history
…n server url
  • Loading branch information
Grisha Kruglov authored and mergify[bot] committed Sep 25, 2020
1 parent edc4327 commit 01306f8
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ interface OAuthAccount : AutoCloseable {
/**
* Fetches the token server endpoint, for authentication using the SAML bearer flow.
*
* @return Token server endpoint URL string
* @return Token server endpoint URL string, `null` if it couldn't be obtained.
*/
fun getTokenServerEndpointURL(): String
suspend fun getTokenServerEndpointURL(): String?

/**
* Get the pairing URL to navigate to on the Authority side (typically a computer).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ class FirefoxAccount internal constructor(
}
}

override fun getTokenServerEndpointURL(): String {
return inner.getTokenServerEndpointURL()
override suspend fun getTokenServerEndpointURL() = withContext(scope.coroutineContext) {
handleFxaExceptions(logger, "getTokenServerEndpointURL", { null }) {
inner.getTokenServerEndpointURL()
}
}

override fun getPairingAuthorityURL(): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -837,12 +837,22 @@ open class FxaAccountManager(
return
}

// NB: this call will call into GlobalAccountManager.authError if necessary.
// Network failures are ignored.
account.getAccessToken(SCOPE_SYNC)?.let {
SyncAuthInfoCache(context).setToCache(
it.asSyncAuthInfo(account.getTokenServerEndpointURL())
)
val accessToken = account.getAccessToken(SCOPE_SYNC)
val tokenServerUrl = if (accessToken != null) {
// Only try to get the endpoint if we have an access token.
account.getTokenServerEndpointURL()
} else {
null
}

if (accessToken != null && tokenServerUrl != null) {
SyncAuthInfoCache(context).setToCache(accessToken.asSyncAuthInfo(tokenServerUrl))
} else {
// At this point, SyncAuthInfoCache may be entirely empty. In this case, we won't be
// able to sync via the background worker. We will attempt to populate SyncAuthInfoCache
// again in `syncNow` (in response to a direct user action) and after application restarts.
logger.warn("Couldn't populate SyncAuthInfoCache. Sync may not work.")
logger.warn("Is null? - accessToken: ${accessToken == null}, tokenServerUrl: ${tokenServerUrl == null}")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import mozilla.components.service.fxa.sync.SyncDispatcher
import mozilla.components.service.fxa.sync.SyncReason
import mozilla.components.service.fxa.sync.SyncStatusObserver
import mozilla.components.concept.base.crash.CrashReporting
import mozilla.components.concept.sync.OAuthScopedKey
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
import mozilla.components.support.test.any
Expand Down Expand Up @@ -91,6 +92,8 @@ internal open class TestableFxaAccountManager(
override fun getAccountStorage(): AccountStorage {
return storage
}

override fun createSyncManager(config: SyncConfig): SyncManager = mock()
}

const val EXPECTED_AUTH_STATE = "goodAuthState"
Expand Down Expand Up @@ -743,7 +746,7 @@ class FxaAccountManagerTest {
return ableToRecoverFromAuthError
}

override fun getTokenServerEndpointURL(): String {
override suspend fun getTokenServerEndpointURL(): String? {
if (tokenServerEndpointUrl != null) return tokenServerEndpointUrl

fail()
Expand Down Expand Up @@ -946,6 +949,14 @@ class FxaAccountManagerTest {

assertEquals(mockAccount, manager.authenticatedAccount())
assertEquals(profile, manager.accountProfile())

val cachedAuthInfo = SyncAuthInfoCache(testContext).getCached()
assertNotNull(cachedAuthInfo)
assertEquals("kid", cachedAuthInfo!!.kid)
assertEquals("someToken", cachedAuthInfo.fxaAccessToken)
assertEquals("k", cachedAuthInfo.syncKey)
assertEquals("some://url", cachedAuthInfo.tokenServerUrl)
assertTrue(cachedAuthInfo.fxaAccessTokenExpiresAt > 0)
}

@Test(expected = FxaPanicException::class)
Expand Down Expand Up @@ -1679,17 +1690,26 @@ class FxaAccountManagerTest {
crashReporter: CrashReporting? = null
): FxaAccountManager {

val accessTokenInfo = AccessTokenInfo(
"testSc0pe", "someToken", OAuthScopedKey("kty", "testSc0pe", "kid", "k"), System.currentTimeMillis() + 1000 * 10
)

`when`(mockAccount.getProfile(ignoreCache = false)).thenReturn(profile)
`when`(mockAccount.beginOAuthFlow(any(), anyString())).thenReturn(AuthFlowUrl(EXPECTED_AUTH_STATE, "auth://url"))
`when`(mockAccount.beginPairingFlow(anyString(), any(), anyString())).thenReturn(AuthFlowUrl(EXPECTED_AUTH_STATE, "auth://url"))
`when`(mockAccount.completeOAuthFlow(anyString(), anyString())).thenReturn(true)
`when`(mockAccount.getAccessToken(anyString())).thenReturn(accessTokenInfo)
`when`(mockAccount.getTokenServerEndpointURL()).thenReturn("some://url")

// There's no account at the start.
`when`(accountStorage.read()).thenReturn(null)

val manager = TestableFxaAccountManager(
testContext,
ServerConfig(Server.RELEASE, "dummyId", "bad://url"),
accountStorage, capabilities, coroutineContext = coroutineContext, crashReporter = crashReporter
ServerConfig(Server.RELEASE, "dummyId", "bad://url"),
accountStorage, capabilities,
SyncConfig(setOf(SyncEngine.History, SyncEngine.Bookmarks), PeriodicSyncConfig()),
coroutineContext = coroutineContext, crashReporter = crashReporter
) {
mockAccount
}
Expand Down

0 comments on commit 01306f8

Please sign in to comment.