Skip to content

Commit 2dc5782

Browse files
authored
Merge branch 'dev' into spetrescu/custom_headers
2 parents 62b6e05 + 8638b3b commit 2dc5782

28 files changed

Lines changed: 1580 additions & 156 deletions

changelog.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
vNext
22
----------
3+
- [PATCH] Emit ipc_strategy telemetry attribute for successful device registration IPC strategy and refactor execute flow to pack protocol request once before strategy retries (#3124)
4+
- [PATCH] Fix Edge browser selection on devices where Microsoft Edge is the default browser: add the rotated Edge signing certificate hash to the Edge BrowserDescriptor and accept multi-signer browsers when any signature intersects the safelist, instead of requiring strict set-equality (resolves MSAL #2414)
5+
- [MINOR] Refactor Auth Tab integration to use provider-based strategy selection. Adds AuthTabStrategyProvider and BrowserLaunchStrategy with Custom Tabs fallback. Compatible with androidx.browser:browser:1.7.0.
6+
- [MINOR] Wire onboarding telemetry hooks into AzureActiveDirectoryWebViewClient for page-transition step capture (broker install, MDM enrollment, Company Portal launch, MFA linking) and last-loaded-domain tracking (#3121)
7+
- [MINOR] Propagate the onboarding telemetry blob through the broker failure path: add BaseException.onboardingBlob and round-trip it through MsalBrokerResultAdapter so callers can emit onboarding telemetry on failure outcomes. Also add an MsalBrokerResultAdapter overload that accepts an onboarding blob on the success path so the broker can attach the finalized blob to the success result bundle (#3123)
38
- [MINOR] Add provisionResourceAccountCredentials API to DeviceRegistrationClientApplication with V0 protocol params/response and add IPPhone to AppRegistry (#3086)
49
- [PATCH] Extend filter-then-clone optimization to deleteAccessTokensWithIntersectingScopes and add telemetry attributes (#3114)
510
- [PATCH] Wire ClientDataInfo through AcquireTokenResult, exceptions (#3109)

common/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ dependencies {
259259
testImplementation "junit:junit:$rootProject.ext.junitVersion"
260260
testImplementation "org.mockito:mockito-core:$rootProject.ext.mockitoCoreVersion"
261261
testImplementation "org.mockito.kotlin:mockito-kotlin:$rootProject.ext.mockitoKotlinVersion"
262-
testImplementation "io.mockk:mockk:1.11.0"
262+
testImplementation "io.mockk:mockk:1.13.10"
263263

264264
testImplementation "org.powermock:powermock-module-junit4:$rootProject.ext.powerMockVersion"
265265
testImplementation "org.powermock:powermock-module-junit4-rule:$rootProject.ext.powerMockVersion"
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.common.internal.providers.oauth2
24+
25+
import android.content.Context
26+
import android.os.Bundle
27+
import androidx.fragment.app.FragmentActivity
28+
import com.microsoft.identity.common.logging.Logger
29+
30+
/**
31+
* Registry for Auth Tab strategy integration provided by consumers outside Common.
32+
*
33+
* Common intentionally does not depend on browser 1.9.0 APIs, so external modules register
34+
* support checks and strategy creation here.
35+
*/
36+
object AuthTabStrategyProvider {
37+
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+
)
48+
49+
@Volatile
50+
private var registration: Registration? = null
51+
52+
private val tag = AuthTabStrategyProvider::class.java.simpleName
53+
54+
/**
55+
* Registers Auth Tab strategy creation and support checking callbacks.
56+
*
57+
* Both callbacks become visible to other threads atomically.
58+
*/
59+
fun register(
60+
factory: (FragmentActivity, (Bundle) -> Unit) -> BrowserLaunchStrategy,
61+
isSupported: (Context, String) -> Boolean
62+
) {
63+
registration = Registration(factory, isSupported)
64+
Logger.info("$tag:register", "Auth Tab strategy provider registered")
65+
}
66+
67+
/**
68+
* Returns true if Auth Tab is supported for the provided browser package.
69+
*/
70+
fun isAuthTabSupported(context: Context, browserPackage: String): Boolean {
71+
val current = registration ?: return false
72+
return current.supportChecker(context, browserPackage)
73+
}
74+
75+
/**
76+
* Creates an Auth Tab browser launch strategy if registered.
77+
*/
78+
fun createStrategy(
79+
activity: FragmentActivity,
80+
onResult: (Bundle) -> Unit
81+
): BrowserLaunchStrategy? {
82+
val current = registration ?: return null
83+
return current.strategyFactory(activity, onResult)
84+
}
85+
86+
/**
87+
* Returns true if an Auth Tab strategy factory has been registered.
88+
*/
89+
fun isAvailable(): Boolean = registration != null
90+
91+
internal fun resetForTest() {
92+
registration = null
93+
}
94+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.common.internal.providers.oauth2
24+
25+
/**
26+
* Strategy contract for launching external browser authentication flows.
27+
*/
28+
interface BrowserLaunchStrategy {
29+
30+
/**
31+
* Launches the browser using the strategy implementation.
32+
*/
33+
fun launch()
34+
35+
/**
36+
* Returns true if [SwitchBrowserActivity] should finish in [SwitchBrowserActivity.onResume]
37+
* when the user returns after launch (for example, pressing back in the browser).
38+
*
39+
* Strategies that receive results through callbacks should return false.
40+
*/
41+
fun handlesCancellationOnResume(): Boolean
42+
43+
/**
44+
* Cleans up strategy resources.
45+
*/
46+
fun cleanup()
47+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.common.internal.providers.oauth2
24+
25+
import android.content.Intent
26+
import androidx.core.net.toUri
27+
import androidx.fragment.app.FragmentActivity
28+
import com.microsoft.identity.common.internal.ui.browser.CustomTabsManager
29+
import com.microsoft.identity.common.java.opentelemetry.AttributeName
30+
import com.microsoft.identity.common.java.opentelemetry.SpanExtension
31+
import com.microsoft.identity.common.logging.Logger
32+
33+
/**
34+
* Browser launch strategy that uses Custom Tabs when supported.
35+
*/
36+
class CustomTabsLaunchStrategy(
37+
private val activity: FragmentActivity
38+
) : BrowserLaunchStrategy {
39+
40+
private val customTabsManager = CustomTabsManager(activity)
41+
42+
companion object {
43+
private val TAG: String = CustomTabsLaunchStrategy::class.java.simpleName
44+
}
45+
46+
override fun launch() {
47+
val methodTag = "$TAG:launch"
48+
val extras = activity.intent.extras
49+
if (extras == null) {
50+
finishWithError(
51+
methodTag,
52+
"Intent extras are missing - Cannot proceed with browser switch"
53+
)
54+
return
55+
}
56+
val browserPackageName = extras.getString(SwitchBrowserActivity.BROWSER_PACKAGE_NAME)
57+
val browserSupportsCustomTabs = extras.getBoolean(SwitchBrowserActivity.BROWSER_SUPPORTS_CUSTOM_TABS, false)
58+
val processUri = extras.getString(SwitchBrowserActivity.PROCESS_URI)
59+
60+
if (browserPackageName.isNullOrBlank()) {
61+
finishWithError(methodTag, "No browser package name found in extras - Cannot proceed with browser switch")
62+
return
63+
}
64+
65+
if (processUri.isNullOrBlank()) {
66+
finishWithError(methodTag, "No process URI found in extras - Cannot proceed with browser switch")
67+
return
68+
}
69+
70+
Logger.info(
71+
methodTag,
72+
"Launching switch browser request on browser: $browserPackageName, Custom Tabs supported: $browserSupportsCustomTabs"
73+
)
74+
75+
val browserIntent: Intent
76+
var isUsingCustomTabs = false
77+
if (browserSupportsCustomTabs) {
78+
Logger.info(methodTag, "CustomTabsService is supported.")
79+
if (!customTabsManager.bind(activity, browserPackageName)) {
80+
Logger.warn(methodTag, "Failed to bind CustomTabsService.")
81+
browserIntent = Intent(Intent.ACTION_VIEW)
82+
} else {
83+
isUsingCustomTabs = true
84+
browserIntent = customTabsManager.customTabsIntent.intent
85+
}
86+
} else {
87+
Logger.warn(methodTag, "CustomTabsService is NOT supported")
88+
browserIntent = Intent(Intent.ACTION_VIEW)
89+
browserIntent.setPackage(browserPackageName)
90+
}
91+
SpanExtension.current().setAttribute(
92+
AttributeName.auth_tab_fallback_to_custom_tabs.name,
93+
isUsingCustomTabs
94+
)
95+
96+
97+
browserIntent.setData(processUri.toUri())
98+
activity.startActivity(browserIntent)
99+
}
100+
101+
override fun handlesCancellationOnResume(): Boolean = true
102+
103+
override fun cleanup() {
104+
customTabsManager.unbind()
105+
}
106+
107+
private fun finishWithError(methodTag: String, message: String) {
108+
Logger.error(methodTag, message, null)
109+
activity.finish()
110+
}
111+
}

0 commit comments

Comments
 (0)