Skip to content

Commit 28adbb7

Browse files
committed
Merge branch 'dev' of https://github.com/AzureAD/microsoft-aut status
thentication-library-common-for-android into fadi/publish-libraries-to-newandroid-feed
2 parents d313d38 + e536e1e commit 28adbb7

20 files changed

Lines changed: 1141 additions & 123 deletions

File tree

changelog.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
vNext
22
----------
3+
- [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)
4+
- [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.
35
- [MINOR] Add provisionResourceAccountCredentials API to DeviceRegistrationClientApplication with V0 protocol params/response and add IPPhone to AppRegistry (#3086)
46
- [PATCH] Extend filter-then-clone optimization to deleteAccessTokensWithIntersectingScopes and add telemetry attributes (#3114)
57
- [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)