Skip to content

Commit 29d9dff

Browse files
committed
Refactor AuthTabStrategyProvider to use a single volatile Registration object for strategy and support checker, enhancing thread safety. Update tests to verify behavior with fallback strategies.
1 parent b256760 commit 29d9dff

3 files changed

Lines changed: 50 additions & 19 deletions

File tree

common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthTabStrategyProvider.kt

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,36 +35,41 @@ import com.microsoft.identity.common.logging.Logger
3535
*/
3636
object AuthTabStrategyProvider {
3737

38-
private val lock = Any()
39-
40-
@Volatile
41-
private var strategyFactory: ((FragmentActivity, (Bundle) -> Unit) -> BrowserLaunchStrategy)? = null
38+
/**
39+
* Immutable holder so both callbacks are published atomically through a single
40+
* volatile reference. This prevents readers from observing a half-registered
41+
* state where, for example, [isAvailable] returns true while
42+
* [isAuthTabSupported] still sees a null support checker.
43+
*/
44+
private data class Registration(
45+
val strategyFactory: (FragmentActivity, (Bundle) -> Unit) -> BrowserLaunchStrategy,
46+
val supportChecker: (Context, String) -> Boolean
47+
)
4248

4349
@Volatile
44-
private var supportChecker: ((Context, String) -> Boolean)? = null
50+
private var registration: Registration? = null
4551

4652
private val tag = AuthTabStrategyProvider::class.java.simpleName
4753

4854
/**
4955
* Registers Auth Tab strategy creation and support checking callbacks.
56+
*
57+
* Both callbacks become visible to other threads atomically.
5058
*/
5159
fun register(
5260
factory: (FragmentActivity, (Bundle) -> Unit) -> BrowserLaunchStrategy,
5361
isSupported: (Context, String) -> Boolean
5462
) {
55-
synchronized(lock) {
56-
strategyFactory = factory
57-
supportChecker = isSupported
58-
}
63+
registration = Registration(factory, isSupported)
5964
Logger.info("$tag:register", "Auth Tab strategy provider registered")
6065
}
6166

6267
/**
6368
* Returns true if Auth Tab is supported for the provided browser package.
6469
*/
6570
fun isAuthTabSupported(context: Context, browserPackage: String): Boolean {
66-
val checker = supportChecker ?: return false
67-
return checker(context, browserPackage)
71+
val current = registration ?: return false
72+
return current.supportChecker(context, browserPackage)
6873
}
6974

7075
/**
@@ -74,19 +79,16 @@ object AuthTabStrategyProvider {
7479
activity: FragmentActivity,
7580
onResult: (Bundle) -> Unit
7681
): BrowserLaunchStrategy? {
77-
val factory = strategyFactory ?: return null
78-
return factory(activity, onResult)
82+
val current = registration ?: return null
83+
return current.strategyFactory(activity, onResult)
7984
}
8085

8186
/**
8287
* Returns true if an Auth Tab strategy factory has been registered.
8388
*/
84-
fun isAvailable(): Boolean = strategyFactory != null
89+
fun isAvailable(): Boolean = registration != null
8590

8691
internal fun resetForTest() {
87-
synchronized(lock) {
88-
strategyFactory = null
89-
supportChecker = null
90-
}
92+
registration = null
9193
}
9294
}

common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/SwitchBrowserActivityTest.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class SwitchBrowserActivityTest {
8080
@Test
8181
fun onCreate_fallsBackToCustomTabsStrategy_whenBrowserPackageMissing() {
8282
mockkObject(AuthTabStrategyProvider)
83+
every { AuthTabStrategyProvider.isAuthTabSupported(any(), any()) } returns false
8384
mockkConstructor(CustomTabsLaunchStrategy::class)
8485
every { anyConstructed<CustomTabsLaunchStrategy>().launch() } just runs
8586

@@ -90,7 +91,31 @@ class SwitchBrowserActivityTest {
9091

9192
Robolectric.buildActivity(SwitchBrowserActivity::class.java, intentWithoutPackage).create()
9293

93-
verify(exactly = 0) { AuthTabStrategyProvider.isAuthTabSupported(any(), any()) }
94+
// isAuthTabSupported is called but returns false (package is empty)
95+
verify(exactly = 1) { AuthTabStrategyProvider.isAuthTabSupported(any(), "") }
96+
// createStrategy should NOT be called because browserPackageName.isNotBlank() fails
97+
verify(exactly = 0) { AuthTabStrategyProvider.createStrategy(any(), any()) }
98+
// CustomTabs strategy is used as fallback
99+
verify(exactly = 1) { anyConstructed<CustomTabsLaunchStrategy>().launch() }
100+
}
101+
102+
@Test
103+
fun onCreate_fallsBackToCustomTabsStrategy_whenCreateStrategyReturnsNull() {
104+
mockkObject(AuthTabStrategyProvider)
105+
// isAuthTabSupported returns true, but createStrategy returns null
106+
// This can occur if the provider is misconfigured or fails to create a strategy
107+
every { AuthTabStrategyProvider.isAuthTabSupported(any(), any()) } returns true
108+
every { AuthTabStrategyProvider.createStrategy(any(), any()) } returns null
109+
mockkConstructor(CustomTabsLaunchStrategy::class)
110+
every { anyConstructed<CustomTabsLaunchStrategy>().launch() } just runs
111+
112+
Robolectric.buildActivity(SwitchBrowserActivity::class.java, getIntent()).create()
113+
114+
// Verify that isAuthTabSupported was called
115+
verify(exactly = 1) { AuthTabStrategyProvider.isAuthTabSupported(any(), any()) }
116+
// Verify that createStrategy was called
117+
verify(exactly = 1) { AuthTabStrategyProvider.createStrategy(any(), any()) }
118+
// Verify that CustomTabsLaunchStrategy.launch() was called as fallback
94119
verify(exactly = 1) { anyConstructed<CustomTabsLaunchStrategy>().launch() }
95120
}
96121

common/src/test/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/SwitchBrowserRequestHandlerTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class SwitchBrowserRequestHandlerTest {
5959
val challenge = mock(SwitchBrowserChallenge::class.java)
6060
`when`(challenge.processUri).thenReturn(Uri.parse("https://login.microsoft.com?state=123"))
6161
`when`(challenge.authorizationUrl).thenReturn("https://auth.com?state=123")
62+
`when`(challenge.redirectUri).thenReturn("https://myapp.example.com/callback")
6263
val browserSelector = // Browser available
6364
IBrowserSelector { _, _ -> Browser("fakeBrowser", emptySet(), "browser", false) }
6465
val handler = SwitchBrowserRequestHandler(mockActivity, browserSelector, null)
@@ -79,6 +80,7 @@ class SwitchBrowserRequestHandlerTest {
7980
val challenge = mock(SwitchBrowserChallenge::class.java)
8081
`when`(challenge.processUri).thenReturn(Uri.parse("https://login.microsoft.com"))
8182
`when`(challenge.authorizationUrl).thenReturn("https://auth.com")
83+
`when`(challenge.redirectUri).thenReturn("https://myapp.example.com/callback")
8284
val browserSelector = // Browser available
8385
IBrowserSelector { _, _ -> Browser("fakeBrowser", emptySet(), "browser", false) }
8486
val handler = SwitchBrowserRequestHandler(mockActivity, browserSelector, null)
@@ -94,6 +96,7 @@ class SwitchBrowserRequestHandlerTest {
9496
val challenge = mock(SwitchBrowserChallenge::class.java)
9597
`when`(challenge.processUri).thenReturn(Uri.parse("https://login.microsoft.com?state=123"))
9698
`when`(challenge.authorizationUrl).thenReturn("https://auth.com?state=123")
99+
`when`(challenge.redirectUri).thenReturn("https://myapp.example.com/callback")
97100
val browserSelector = IBrowserSelector { _, _ -> null } // No browser available
98101
val handler = SwitchBrowserRequestHandler(activity, browserSelector, null)
99102
val exception = Assert.assertThrows(ClientException::class.java) {
@@ -111,6 +114,7 @@ class SwitchBrowserRequestHandlerTest {
111114
val challenge = mock(SwitchBrowserChallenge::class.java)
112115
`when`(challenge.processUri).thenReturn(Uri.parse("https://login.microsoft.com?state=123"))
113116
`when`(challenge.authorizationUrl).thenReturn("https://auth.com?state=456")
117+
`when`(challenge.redirectUri).thenReturn("https://myapp.example.com/callback")
114118
val browserSelector = // Browser available
115119
IBrowserSelector { _, _ -> Browser("fakeBrowser", emptySet(), "browser", false) }
116120
val handler = SwitchBrowserRequestHandler(mockActivity, browserSelector, null)

0 commit comments

Comments
 (0)