Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
vNext
----------
- [MINOR] Add SpanContext support to PasskeyWebListener and WebViewAuthorizationFragment (#2850)
- [MINOR] Add additional allowed origins for PasskeyWebListener (#2839)
- [PATCH] Add JavascriptInterface rules to consumer proguard rules (#2837)
- [MINOR] Add optimized saveAndLoadAggregatedAccountData method in BrokerOAuth2TokenCache (#2832)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.microsoft.identity.common.BuildConfig
import com.microsoft.identity.common.internal.ui.webview.AzureActiveDirectoryWebViewClient
import com.microsoft.identity.common.java.exception.ClientException
import com.microsoft.identity.common.logging.Logger
import io.opentelemetry.api.trace.SpanContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand All @@ -51,10 +52,12 @@ import java.util.concurrent.atomic.AtomicBoolean
*
* @property coroutineScope Scope for launching credential operations.
* @property credentialManagerHandler Handles passkey creation and retrieval.
* @property spanContext Optional OpenTelemetry span context for tracing.
*/
class PasskeyWebListener(
private val coroutineScope: CoroutineScope,
private val credentialManagerHandler: CredentialManagerHandler,
private val spanContext: SpanContext? = null
) : WebViewCompat.WebMessageListener {

/** Tracks if a WebAuthN request is currently pending. Only one request is allowed at a time. */
Expand Down Expand Up @@ -106,7 +109,11 @@ class PasskeyWebListener(
methodTag,
"Received WebAuthN request of type: ${webAuthNMessage.type} from origin: $sourceOrigin"
)
val passkeyReplyChannel = PasskeyReplyChannel(javaScriptReplyProxy, webAuthNMessage.type)
val passkeyReplyChannel = PasskeyReplyChannel(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is spanContext then being used to parent the new span?

replyProxy = javaScriptReplyProxy,
requestType = webAuthNMessage.type,
spanContext = spanContext
)

// Only allow one request at a time.
if (havePendingRequest.get()) {
Expand Down Expand Up @@ -228,7 +235,10 @@ class PasskeyWebListener(
messageData: String?,
javaScriptReplyProxy: JavaScriptReplyProxy
): WebAuthNMessage? {
val passkeyReplyChannel = PasskeyReplyChannel(javaScriptReplyProxy)
val passkeyReplyChannel = PasskeyReplyChannel(
replyProxy = javaScriptReplyProxy,
spanContext = spanContext
)
return runCatching {
if (messageData.isNullOrBlank()) {
throw ClientException(ClientException.MISSING_PARAMETER, "Message data is null or blank")
Expand Down Expand Up @@ -300,13 +310,15 @@ class PasskeyWebListener(
* @param webView WebView to attach to.
* @param activity Activity context for credential operations.
* @param webClient WebViewClient to inject JavaScript into.
* @param spanContext Optional OpenTelemetry span context for tracing.
* @return True if successfully hooked, false otherwise.
*/
@JvmStatic
fun hook(
webView: WebView,
activity: Activity,
webClient: AzureActiveDirectoryWebViewClient
webClient: AzureActiveDirectoryWebViewClient,
spanContext: SpanContext?
): Boolean {
val methodTag = "$TAG:hook"

Expand All @@ -330,7 +342,8 @@ class PasskeyWebListener(
PasskeyOriginRulesManager.getAllowedOriginRules(),
PasskeyWebListener(
coroutineScope = CoroutineScope(Dispatchers.Default),
credentialManagerHandler = CredentialManagerHandler(activity)
credentialManagerHandler = CredentialManagerHandler(activity),
spanContext = spanContext
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,8 @@ private void setupPasskeyWebListener(@NonNull final WebView webView,
final String methodTag = TAG + ":setupPasskeyWebListener";
final String passkeyProtocolHeader = mRequestHeaders.get(FidoConstants.PASSKEY_PROTOCOL_HEADER_NAME);
if (FidoConstants.PASSKEY_PROTOCOL_HEADER_AUTH_AND_REG.equals(passkeyProtocolHeader)) {
final boolean passkeyWebListenerHooked = PasskeyWebListener.hook(webView, requireActivity(), webViewClient);
final SpanContext spanContext = requireActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) requireActivity()).getSpanContext() : null;
final boolean passkeyWebListenerHooked = PasskeyWebListener.hook(webView, requireActivity(), webViewClient, spanContext);
if (!passkeyWebListenerHooked) {
Logger.warn(methodTag, "PasskeyWebListener hook failed, Downgrading to auth only.");
// Downgrade to auth only
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ class PasskeyWebListenerTest {
@Config(sdk = [Build.VERSION_CODES.O_MR1]) // API 27 - below minimum
fun `hook returns false on API below 28`() {
// When
val result = PasskeyWebListener.hook(webView, activity, mockWebViewClient)
val result = PasskeyWebListener.hook(webView, activity, mockWebViewClient, null)

// Then
assertFalse(result)
Expand All @@ -503,7 +503,7 @@ class PasskeyWebListenerTest {
} just Runs

// When
val result = PasskeyWebListener.hook(webView, activity, mockWebViewClient)
val result = PasskeyWebListener.hook(webView, activity, mockWebViewClient, null)

// Then
assertTrue(result)
Expand All @@ -530,7 +530,7 @@ class PasskeyWebListenerTest {
every { WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) } returns false

// When
val result = PasskeyWebListener.hook(webView, activity, mockWebViewClient)
val result = PasskeyWebListener.hook(webView, activity, mockWebViewClient, null)

// Then
assertFalse(result)
Expand Down