From 04b2eea3c97e53bca6764957f952f70bb164039d Mon Sep 17 00:00:00 2001 From: Melissa Ahn Date: Thu, 12 Jun 2025 15:33:09 -0700 Subject: [PATCH 1/9] Using Baggage to propagate some attributes --- .../internal/AuthenticationConstants.java | 2 + .../internal/fido/AuthFidoChallengeHandler.kt | 26 ++++- .../oauth2/AuthorizationActivity.java | 27 +++++ .../oauth2/AuthorizationActivityFactory.kt | 7 ++ .../AzureActiveDirectoryWebViewClient.java | 5 +- .../fido/AuthFidoChallengeHandlerTest.kt | 4 +- common4j/build.gradle | 1 + .../java/opentelemetry/AttributeName.java | 25 ++++- .../java/opentelemetry/BaggageUtility.java | 70 ++++++++++++ .../TextMapPropagatorExtension.java | 101 ++++++++++++++++++ 10 files changed, 258 insertions(+), 10 deletions(-) create mode 100644 common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageUtility.java create mode 100644 common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java diff --git a/common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java b/common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java index 1ec8ee73d6..ceffad06f5 100644 --- a/common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java +++ b/common/src/main/java/com/microsoft/identity/common/adal/internal/AuthenticationConstants.java @@ -2013,6 +2013,8 @@ public static final class AuthorizationIntentKey { public static final String WEB_VIEW_ZOOM_CONTROLS_ENABLED = "com.microsoft.identity.web.view.zoom.controls.enabled"; public static final String WEB_VIEW_ZOOM_ENABLED = "com.microsoft.identity.web.view.zoom.enabled"; + + public static final String OTEL_CONTEXT_CARRIER = "otel_context_carrier"; } public static final class AuthorizationIntentAction { diff --git a/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt b/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt index 832b5808c0..c47b75eb1a 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt @@ -33,9 +33,10 @@ import com.microsoft.identity.common.java.opentelemetry.AttributeName import com.microsoft.identity.common.java.opentelemetry.OTelUtility import com.microsoft.identity.common.java.opentelemetry.SpanName import com.microsoft.identity.common.logging.Logger +import io.opentelemetry.api.baggage.Baggage import io.opentelemetry.api.trace.Span -import io.opentelemetry.api.trace.SpanContext import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.context.Context import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch import java.util.* @@ -51,18 +52,33 @@ import java.util.* class AuthFidoChallengeHandler ( private val fidoManager: IFidoManager, private val webView: WebView, - private val spanContext : SpanContext?, + private val oTelContext: Context?, private val lifecycleOwner: LifecycleOwner? ) : IChallengeHandler { val TAG = AuthFidoChallengeHandler::class.simpleName.toString() + val parentAttributeNames = arrayListOf( + AttributeName.correlation_id, + AttributeName.tenant_id, + AttributeName.account_type, + AttributeName.calling_package_name + ) override fun processChallenge(fidoChallenge: FidoChallenge): Void? { val methodTag = "$TAG:processChallenge" Logger.info(methodTag, "Processing FIDO challenge.") - val span = if (spanContext != null) { - OTelUtility.createSpanFromParent(SpanName.Fido.name, spanContext) + val span : Span + if (oTelContext != null) { + val parentSpan = Span.fromContext(oTelContext) + val spanContext = parentSpan.spanContext + span = OTelUtility.createSpanFromParent(SpanName.Fido.name, spanContext) + val baggage = Baggage.fromContext(oTelContext) + parentAttributeNames.forEach { attributeName -> + baggage.getEntryValue(attributeName.name)?.let { value -> + span.setAttribute(attributeName.name, value) + } + } } else { - OTelUtility.createSpan(SpanName.Fido.name) + span = OTelUtility.createSpan(SpanName.Fido.name) } span.setAttribute( AttributeName.fido_challenge_handler.name, diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java index b956822301..e0b6e0362e 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java @@ -22,6 +22,8 @@ // THE SOFTWARE. package com.microsoft.identity.common.internal.providers.oauth2; +import static com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.OTEL_CONTEXT_CARRIER; + import android.os.Bundle; import androidx.annotation.Nullable; @@ -31,9 +33,14 @@ import com.microsoft.identity.common.internal.util.CommonMoshiJsonAdapter; import com.microsoft.identity.common.java.exception.TerminalException; import com.microsoft.identity.common.java.opentelemetry.SerializableSpanContext; +import com.microsoft.identity.common.java.opentelemetry.TextMapPropagatorExtension; import com.microsoft.identity.common.logging.Logger; +import java.util.HashMap; +import java.util.Map; + import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; import lombok.Getter; import lombok.experimental.Accessors; @@ -43,6 +50,11 @@ public class AuthorizationActivity extends DualScreenActivity { @Getter @Accessors(prefix = "m") private SpanContext mSpanContext; + + @Getter + @Accessors(prefix = "m") + private Context mOtelContext; + private AuthorizationFragment mFragment; public AuthorizationFragment getFragment() { @@ -60,6 +72,21 @@ public void onCreate(@Nullable Bundle savedInstanceState) { getIntent().getExtras().getString(SerializableSpanContext.SERIALIZABLE_SPAN_CONTEXT), SerializableSpanContext.class ); + // Extract OtelContext from the carrier if it exists + final Map carrier; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + carrier = getIntent().getExtras() != null ? + getIntent().getExtras().getSerializable(OTEL_CONTEXT_CARRIER, HashMap.class) : + new HashMap<>(); + } else { + @SuppressWarnings("unchecked") + Map temp = getIntent().getExtras() != null ? + (Map) getIntent().getExtras().getSerializable(OTEL_CONTEXT_CARRIER) : + new HashMap<>(); + carrier = temp; + } + mOtelContext = TextMapPropagatorExtension.extract(carrier); + } catch (final TerminalException e) { // Don't want to block any features if an error occurs during deserialization of the span context. mSpanContext = null; diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt index 76e6d56d88..2c02120d15 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt @@ -26,6 +26,7 @@ import android.content.Intent import android.os.Bundle import androidx.fragment.app.Fragment import com.microsoft.identity.common.adal.internal.AuthenticationConstants +import com.microsoft.identity.common.adal.internal.AuthenticationConstants.AuthorizationIntentKey.OTEL_CONTEXT_CARRIER import com.microsoft.identity.common.internal.msafederation.getIdProviderExtraQueryParamForAuthorization import com.microsoft.identity.common.internal.msafederation.getIdProviderHeadersForAuthorization import com.microsoft.identity.common.internal.msafederation.google.SignInWithGoogleApi.Companion.getInstance @@ -40,8 +41,10 @@ import com.microsoft.identity.common.java.exception.ClientException import com.microsoft.identity.common.java.logging.DiagnosticContext import com.microsoft.identity.common.java.opentelemetry.SerializableSpanContext import com.microsoft.identity.common.java.opentelemetry.SpanExtension +import com.microsoft.identity.common.java.opentelemetry.TextMapPropagatorExtension import com.microsoft.identity.common.java.ui.AuthorizationAgent import com.microsoft.identity.common.java.util.CommonURIBuilder +import io.opentelemetry.context.Context import java.net.URISyntaxException @@ -118,6 +121,10 @@ object AuthorizationActivityFactory { .build() ) ) + putExtra( + OTEL_CONTEXT_CARRIER, + TextMapPropagatorExtension.inject(Context.current()) + ) if (parameters.sourceLibraryName != null) { putExtra(PRODUCT, parameters.sourceLibraryName) } diff --git a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java index ef01185661..976eb7ae74 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java @@ -99,6 +99,7 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; /** @@ -236,7 +237,7 @@ private boolean handleUrl(final WebView view, final String url) { Logger.info(methodTag,"WebView detected request for passkey protocol."); final FidoChallenge challenge = FidoChallenge.createFromRedirectUri(url); final Activity currentActivity = getActivity(); - final SpanContext spanContext = currentActivity instanceof AuthorizationActivity ? ((AuthorizationActivity)currentActivity).getSpanContext() : null; + final Context oTelContext = currentActivity instanceof AuthorizationActivity ? ((AuthorizationActivity)currentActivity).getOtelContext() : null; // The legacyManager should only be getting added if the device is on Android 13 or lower and the library is MSAL/OneAuth with fragment or dialog mode. // The legacyManager logic should be removed once a larger majority of users are on Android 14+. final IFidoManager legacyManager = @@ -252,7 +253,7 @@ private boolean handleUrl(final WebView view, final String url) { legacyManager ), view, - spanContext, + oTelContext, ViewTreeLifecycleOwner.get(view)); challengeHandler.processChallenge(challenge); } else if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_ATTACH_NEW_PRT_HEADER_WHEN_NONCE_EXPIRED) && isNonceRedirect(formattedURL)) { diff --git a/common/src/test/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandlerTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandlerTest.kt index 3eb30a008b..afa1f277a3 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandlerTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandlerTest.kt @@ -66,7 +66,7 @@ class AuthFidoChallengeHandlerTest { authFidoChallengeHandler = AuthFidoChallengeHandler( fidoManager = testFidoManager, webView = webView, - spanContext = null, + oTelContext = null, lifecycleOwner = testLifecycleOwner ) } @@ -126,7 +126,7 @@ class AuthFidoChallengeHandlerTest { authFidoChallengeHandler = AuthFidoChallengeHandler( fidoManager = testFidoManager, webView = webView, - spanContext = null, + oTelContext = null, lifecycleOwner = null ) assertFalse(webView.urlLoaded) diff --git a/common4j/build.gradle b/common4j/build.gradle index 997eee0854..e053525ef0 100644 --- a/common4j/build.gradle +++ b/common4j/build.gradle @@ -250,6 +250,7 @@ dependencies { resolvableTestFixturesImplementation "org.robolectric:junit:$rootProject.ext.robolectricVersion" implementation("io.opentelemetry:opentelemetry-api:$rootProject.ext.openTelemetryVersion") + implementation "io.opentelemetry:opentelemetry-sdk:$rootProject.ext.openTelemetryVersion" } sourceCompatibility = "1.8" diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java index 5c93535b33..eec71e7fc6 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java @@ -363,5 +363,28 @@ public enum AttributeName { /** * Records the if the broker handled a switch browser resume, */ - is_switch_browser_resume_handled + is_switch_browser_resume_handled, + + /** + * The tenant id for the home tenant of the account for which PRT is required. + */ + tenant_id, + + /** + * Indicates the type of account such as AAD or MSA. + */ + account_type, + + /** + * Indicates the broker app that emits the event. + * The broker is not necessarily the active broker. + * e.g. An inactive broker app might be invoked during OnUpgrade. + * (It should be renamed, but that would mess up the dashboard) + */ + active_broker_package_name, + + /** + * Indicates the current broker package name processing the request. + */ + current_broker_package_name } diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageUtility.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageUtility.java new file mode 100644 index 0000000000..7f50c786b3 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageUtility.java @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.common.java.opentelemetry; + +import java.util.Arrays; +import java.util.List; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.sdk.trace.ReadableSpan; + +/** + * Utility class for working with OpenTelemetry Baggage objects. + */ +public class BaggageUtility { + + private static final String TAG = BaggageUtility.class.getSimpleName(); + + /** + * Default constructor. + */ + private BaggageUtility() { + // Utility class, private constructor to prevent instantiation + } + + /** + * Extracts baggage items from a ReadableSpan based on the specified attribute names. + * + * @param span The span from which to extract baggage data. + * @param attributeNames List of attribute names to extract from the span. + * @return A Baggage object containing extracted attributes. + */ + public static Baggage getBaggageFromReadableSpan(final Span span, final List attributeNames) { + final BaggageBuilder baggageBuilder = Baggage.builder(); + if (span instanceof ReadableSpan && attributeNames != null && !attributeNames.isEmpty()) { + ReadableSpan readableSpan = (ReadableSpan) span; + attributeNames.forEach(attributeName -> { + final String value = readableSpan.getAttribute(AttributeKey.stringKey(attributeName)); + + if (value != null && !value.isEmpty()) { + baggageBuilder.put(attributeName, value); + } + }); + } + + return baggageBuilder.build(); + } +} diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java new file mode 100644 index 0000000000..bf48ca7276 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.common.java.opentelemetry; + +import java.util.HashMap; +import java.util.Map; + +import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; + +/** + * Extension class for handling OpenTelemetry context propagation. + * Provides utility methods for injecting and extracting context using W3CBaggagePropagator. + */ +public final class TextMapPropagatorExtension { + + private static final TextMapPropagator PROPAGATOR = TextMapPropagator.composite( + W3CTraceContextPropagator.getInstance(), + W3CBaggagePropagator.getInstance() + ); + + /** + * Private constructor to prevent instantiation. + */ + private TextMapPropagatorExtension() { + // This utility class is not meant to be instantiated + } + + /** + * Injects the current context into a carrier using W3CBaggagePropagator. + * + * @param context The context to inject. If null, the current context will be used. + * @return A map containing the injected context properties. + */ + public static HashMap inject(Context context) { + final HashMap carrier = new HashMap<>(); + final Context contextToInject = context != null ? context : Context.current(); + + final TextMapSetter> setter = new TextMapSetter>() { + @Override + public void set(Map carrier, String key, String value) { + if (carrier != null && key != null && value != null) { + carrier.put(key, value); + } + } + }; + + PROPAGATOR.inject(contextToInject, carrier, setter); + return carrier; + } + + /** + * Extracts context from a carrier map using W3CBaggagePropagator. + * + * @param carrier The carrier containing context information. + * @return The extracted context, or the current context if extraction fails. + */ + public static Context extract(Map carrier) { + if (carrier == null || carrier.isEmpty()) { + return Context.current(); + } + + final TextMapGetter> getter = new TextMapGetter>() { + @Override + public String get(Map carrier, String key) { + return carrier.get(key); + } + + @Override + public Iterable keys(Map carrier) { + return carrier.keySet(); + } + }; + + return PROPAGATOR.extract(Context.current(), carrier, getter); + } +} From f3fbae9131bae473ba64ec0b11c8f4a2b4cb6a45 Mon Sep 17 00:00:00 2001 From: Melissa Ahn Date: Thu, 12 Jun 2025 15:42:52 -0700 Subject: [PATCH 2/9] changelog --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 847d6392db..aa76341cdb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,6 +5,7 @@ vNext - [MINOR] Expose a JavaScript API in brokered Webviews to facilitate Improved Same Device NumberMatch (#2617) - [MINOR] Add API for resource account provisioning (API only) (#2640) - [PATCH] Fixing error in construction of LegacyFido2ApiManager (#2654) +- [MINOR] Using Baggage to propagate attributes from parent Span Version 21.1.0 ---------- From 8988bfc90ed8203b480e25f4a8bb27a49cdcdf49 Mon Sep 17 00:00:00 2001 From: Melissa Ahn Date: Thu, 12 Jun 2025 17:59:01 -0700 Subject: [PATCH 3/9] edits --- changelog.txt | 2 +- .../common/internal/fido/AuthFidoChallengeHandler.kt | 2 +- .../java/opentelemetry/TextMapPropagatorExtension.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/changelog.txt b/changelog.txt index aa76341cdb..0e175635e0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,7 +5,7 @@ vNext - [MINOR] Expose a JavaScript API in brokered Webviews to facilitate Improved Same Device NumberMatch (#2617) - [MINOR] Add API for resource account provisioning (API only) (#2640) - [PATCH] Fixing error in construction of LegacyFido2ApiManager (#2654) -- [MINOR] Using Baggage to propagate attributes from parent Span +- [MINOR] Using Baggage to propagate attributes from parent Span (##2671) Version 21.1.0 ---------- diff --git a/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt b/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt index c47b75eb1a..b6a0ebf990 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt @@ -56,7 +56,7 @@ class AuthFidoChallengeHandler ( private val lifecycleOwner: LifecycleOwner? ) : IChallengeHandler { val TAG = AuthFidoChallengeHandler::class.simpleName.toString() - val parentAttributeNames = arrayListOf( + private val parentAttributeNames = arrayListOf( AttributeName.correlation_id, AttributeName.tenant_id, AttributeName.account_type, diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java index bf48ca7276..e1eee4b6dd 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java @@ -34,7 +34,7 @@ /** * Extension class for handling OpenTelemetry context propagation. - * Provides utility methods for injecting and extracting context using W3CBaggagePropagator. + * Provides utility methods for injecting and extracting context which contains Baggage and SpanContext. */ public final class TextMapPropagatorExtension { @@ -51,7 +51,7 @@ private TextMapPropagatorExtension() { } /** - * Injects the current context into a carrier using W3CBaggagePropagator. + * Injects the current context into a carrier. * * @param context The context to inject. If null, the current context will be used. * @return A map containing the injected context properties. @@ -74,7 +74,7 @@ public void set(Map carrier, String key, String value) { } /** - * Extracts context from a carrier map using W3CBaggagePropagator. + * Extracts context from a carrier map. * * @param carrier The carrier containing context information. * @return The extracted context, or the current context if extraction fails. From ecf9a9b555a0488b00612bc3e1f2006613dded5a Mon Sep 17 00:00:00 2001 From: Melissa Ahn Date: Thu, 12 Jun 2025 21:36:02 -0700 Subject: [PATCH 4/9] makeBaggageCurrent --- ...gageUtility.java => BaggageExtension.java} | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) rename common4j/src/main/com/microsoft/identity/common/java/opentelemetry/{BaggageUtility.java => BaggageExtension.java} (72%) diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageUtility.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java similarity index 72% rename from common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageUtility.java rename to common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java index 7f50c786b3..d719416087 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageUtility.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java @@ -22,26 +22,29 @@ // THE SOFTWARE. package com.microsoft.identity.common.java.opentelemetry; -import java.util.Arrays; +import com.microsoft.identity.common.java.logging.Logger; + import java.util.List; import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.baggage.BaggageBuilder; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.trace.ReadableSpan; /** * Utility class for working with OpenTelemetry Baggage objects. */ -public class BaggageUtility { +public class BaggageExtension { - private static final String TAG = BaggageUtility.class.getSimpleName(); + private static final String TAG = BaggageExtension.class.getSimpleName(); /** * Default constructor. */ - private BaggageUtility() { + private BaggageExtension() { // Utility class, private constructor to prevent instantiation } @@ -67,4 +70,24 @@ public static Baggage getBaggageFromReadableSpan(final Span span, final List Date: Tue, 24 Jun 2025 16:17:49 -0700 Subject: [PATCH 5/9] changes --- common4j/build.gradle | 1 - .../java/opentelemetry/BaggageExtension.java | 93 ------------------- .../java/opentelemetry/SpanExtension.java | 2 +- 3 files changed, 1 insertion(+), 95 deletions(-) delete mode 100644 common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java diff --git a/common4j/build.gradle b/common4j/build.gradle index 528a0fdad1..380c8f1098 100644 --- a/common4j/build.gradle +++ b/common4j/build.gradle @@ -252,7 +252,6 @@ dependencies { resolvableTestFixturesImplementation "org.robolectric:junit:$rootProject.ext.robolectricVersion" implementation("io.opentelemetry:opentelemetry-api:$rootProject.ext.openTelemetryVersion") - implementation "io.opentelemetry:opentelemetry-sdk:$rootProject.ext.openTelemetryVersion" } sourceCompatibility = "1.8" diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java deleted file mode 100644 index d719416087..0000000000 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// All rights reserved. -// -// This code is licensed under the MIT License. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files(the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions : -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -package com.microsoft.identity.common.java.opentelemetry; - -import com.microsoft.identity.common.java.logging.Logger; - -import java.util.List; - -import io.opentelemetry.api.baggage.Baggage; -import io.opentelemetry.api.baggage.BaggageBuilder; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.context.Context; -import io.opentelemetry.context.Scope; -import io.opentelemetry.sdk.trace.ReadableSpan; - -/** - * Utility class for working with OpenTelemetry Baggage objects. - */ -public class BaggageExtension { - - private static final String TAG = BaggageExtension.class.getSimpleName(); - - /** - * Default constructor. - */ - private BaggageExtension() { - // Utility class, private constructor to prevent instantiation - } - - /** - * Extracts baggage items from a ReadableSpan based on the specified attribute names. - * - * @param span The span from which to extract baggage data. - * @param attributeNames List of attribute names to extract from the span. - * @return A Baggage object containing extracted attributes. - */ - public static Baggage getBaggageFromReadableSpan(final Span span, final List attributeNames) { - final BaggageBuilder baggageBuilder = Baggage.builder(); - if (span instanceof ReadableSpan && attributeNames != null && !attributeNames.isEmpty()) { - ReadableSpan readableSpan = (ReadableSpan) span; - attributeNames.forEach(attributeName -> { - final String value = readableSpan.getAttribute(AttributeKey.stringKey(attributeName)); - - if (value != null && !value.isEmpty()) { - baggageBuilder.put(attributeName, value); - } - }); - } - - return baggageBuilder.build(); - } - - /** - * Makes the provided Baggage current in the context, catching any exceptions silently. - * This is useful in scenarios where Baggage propagation should not interrupt normal operation flow. - * - * @param baggage The Baggage to make current. - * @return the resulting scope. - */ - public static Scope makeBaggageCurrent(final Baggage baggage) { - if (baggage == null) { - return SpanExtension.NoopScope.INSTANCE; - } - - try { - return baggage.storeInContext(Context.current()).makeCurrent(); - } catch (Exception e) { - Logger.error(TAG + ":makeBaggageCurrent", e.getMessage(), e); - return SpanExtension.NoopScope.INSTANCE; - } - } -} diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanExtension.java index 55d6b32dd7..71ffa2fbf1 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanExtension.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanExtension.java @@ -95,7 +95,7 @@ public static Scope makeCurrentSpan(@NonNull final Span span) { * default Noop implementation in {@link io.opentelemetry.context.ThreadLocalContextStorage}. * We just made a custom one since the default one is package-private. */ - enum NoopScope implements Scope { + public enum NoopScope implements Scope { INSTANCE; @Override From 8203a624b2294deb7cb06c32740e3a2f2860a956 Mon Sep 17 00:00:00 2001 From: Melissa Ahn Date: Sun, 29 Jun 2025 17:35:21 -0700 Subject: [PATCH 6/9] addressing more comments --- .../internal/fido/AuthFidoChallengeHandler.kt | 7 +- .../oauth2/AuthorizationActivity.java | 34 ++++---- .../oauth2/AuthorizationActivityFactory.kt | 4 +- .../java/opentelemetry/BaggageExtension.java | 77 +++++++++++++++++ .../java/opentelemetry/NoopBaggage.java | 63 ++++++++++++++ .../opentelemetry/OtelContextExtension.java | 24 +++++- .../java/opentelemetry/SpanExtension.java | 16 ++++ .../TextMapPropagatorExtension.java | 86 ++++++++++++------- 8 files changed, 254 insertions(+), 57 deletions(-) create mode 100644 common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java create mode 100644 common4j/src/main/com/microsoft/identity/common/java/opentelemetry/NoopBaggage.java diff --git a/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt b/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt index b6a0ebf990..deb68e9160 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt @@ -30,10 +30,11 @@ import com.microsoft.identity.common.internal.ui.webview.challengehandlers.IChal import com.microsoft.identity.common.java.constants.FidoConstants import com.microsoft.identity.common.java.constants.FidoConstants.Companion.PASSKEY_PROTOCOL_ERROR_PREFIX_STRING import com.microsoft.identity.common.java.opentelemetry.AttributeName +import com.microsoft.identity.common.java.opentelemetry.BaggageExtension import com.microsoft.identity.common.java.opentelemetry.OTelUtility +import com.microsoft.identity.common.java.opentelemetry.SpanExtension import com.microsoft.identity.common.java.opentelemetry.SpanName import com.microsoft.identity.common.logging.Logger -import io.opentelemetry.api.baggage.Baggage import io.opentelemetry.api.trace.Span import io.opentelemetry.api.trace.StatusCode import io.opentelemetry.context.Context @@ -68,10 +69,10 @@ class AuthFidoChallengeHandler ( Logger.info(methodTag, "Processing FIDO challenge.") val span : Span if (oTelContext != null) { - val parentSpan = Span.fromContext(oTelContext) + val parentSpan = SpanExtension.fromContext(oTelContext) val spanContext = parentSpan.spanContext span = OTelUtility.createSpanFromParent(SpanName.Fido.name, spanContext) - val baggage = Baggage.fromContext(oTelContext) + val baggage = BaggageExtension.fromContext(oTelContext) parentAttributeNames.forEach { attributeName -> baggage.getEntryValue(attributeName.name)?.let { value -> span.setAttribute(attributeName.name, value) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java index e0b6e0362e..a6547af516 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java @@ -34,10 +34,10 @@ import com.microsoft.identity.common.java.exception.TerminalException; import com.microsoft.identity.common.java.opentelemetry.SerializableSpanContext; import com.microsoft.identity.common.java.opentelemetry.TextMapPropagatorExtension; +import com.microsoft.identity.common.java.util.StringUtil; import com.microsoft.identity.common.logging.Logger; import java.util.HashMap; -import java.util.Map; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Context; @@ -66,36 +66,32 @@ public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final String methodTag = TAG + ":onCreate"; - if (getIntent().getExtras() != null) { + final Bundle bundle = getIntent().getExtras(); + if (bundle != null) { try { - mSpanContext = new CommonMoshiJsonAdapter().fromJson( - getIntent().getExtras().getString(SerializableSpanContext.SERIALIZABLE_SPAN_CONTEXT), + String spanContextJson = bundle.getString(SerializableSpanContext.SERIALIZABLE_SPAN_CONTEXT); + mSpanContext = StringUtil.isNullOrEmpty(spanContextJson) ? null : new CommonMoshiJsonAdapter().fromJson( + spanContextJson, SerializableSpanContext.class ); - // Extract OtelContext from the carrier if it exists - final Map carrier; + + final HashMap carrier; + // For Android Tiramisu and above, we use the new Serializable interface, which is a bit safer because it performs type checking at the framework level. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { - carrier = getIntent().getExtras() != null ? - getIntent().getExtras().getSerializable(OTEL_CONTEXT_CARRIER, HashMap.class) : - new HashMap<>(); + carrier = bundle.getSerializable(OTEL_CONTEXT_CARRIER, HashMap.class); } else { - @SuppressWarnings("unchecked") - Map temp = getIntent().getExtras() != null ? - (Map) getIntent().getExtras().getSerializable(OTEL_CONTEXT_CARRIER) : - new HashMap<>(); - carrier = temp; + carrier = (HashMap) bundle.getSerializable(OTEL_CONTEXT_CARRIER); } mOtelContext = TextMapPropagatorExtension.extract(carrier); - - } catch (final TerminalException e) { - // Don't want to block any features if an error occurs during deserialization of the span context. - mSpanContext = null; + } catch (final TerminalException | ClassCastException | NullPointerException e) { + // Don't want to block any features if an error occurs during deserialization. + Logger.warn(methodTag, "Exception thrown during extraction: " + e.getMessage()); } } final Fragment fragment = AuthorizationActivityFactory.getAuthorizationFragmentFromStartIntent(getIntent()); if (fragment instanceof AuthorizationFragment) { mFragment = (AuthorizationFragment) fragment; - mFragment.setInstanceState(getIntent().getExtras()); + mFragment.setInstanceState(bundle); } else { final IllegalStateException ex = new IllegalStateException("Unexpected fragment type."); Logger.error(methodTag, "Did not receive AuthorizationFragment from factory", ex); diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt index 2c02120d15..d61b98e562 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt @@ -39,12 +39,12 @@ import com.microsoft.identity.common.java.AuthenticationConstants.SdkPlatformFie import com.microsoft.identity.common.java.configuration.LibraryConfiguration import com.microsoft.identity.common.java.exception.ClientException import com.microsoft.identity.common.java.logging.DiagnosticContext +import com.microsoft.identity.common.java.opentelemetry.OtelContextExtension import com.microsoft.identity.common.java.opentelemetry.SerializableSpanContext import com.microsoft.identity.common.java.opentelemetry.SpanExtension import com.microsoft.identity.common.java.opentelemetry.TextMapPropagatorExtension import com.microsoft.identity.common.java.ui.AuthorizationAgent import com.microsoft.identity.common.java.util.CommonURIBuilder -import io.opentelemetry.context.Context import java.net.URISyntaxException @@ -123,7 +123,7 @@ object AuthorizationActivityFactory { ) putExtra( OTEL_CONTEXT_CARRIER, - TextMapPropagatorExtension.inject(Context.current()) + TextMapPropagatorExtension.inject(OtelContextExtension.current()) ) if (parameters.sourceLibraryName != null) { putExtra(PRODUCT, parameters.sourceLibraryName) diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java new file mode 100644 index 0000000000..58f892c344 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/BaggageExtension.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.common.java.opentelemetry; + +import com.microsoft.identity.common.java.logging.Logger; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; + +/** + * Utility class for working with OpenTelemetry Baggage objects. + */ +public class BaggageExtension { + + private static final String TAG = BaggageExtension.class.getSimpleName(); + + /** + * Default constructor. + */ + private BaggageExtension() { + // Utility class, private constructor to prevent instantiation + } + + /** + * Makes the provided Baggage current in the context, catching any exceptions silently. + * This is useful in scenarios where Baggage propagation should not interrupt normal operation flow. + * + * @param baggage The Baggage to make current. + * @return the resulting scope. + */ + public static Scope makeBaggageCurrent(final Baggage baggage) { + try { + if (baggage == null) { + return SpanExtension.NoopScope.INSTANCE; + } + return baggage.storeInContext(Context.current()).makeCurrent(); + } catch (final Exception e) { + Logger.error(TAG + ":makeBaggageCurrent", "Failed to make baggage current", e); + return SpanExtension.NoopScope.INSTANCE; + } + } + + /** + * Returns the current Baggage from the context, or a NoopBaggage if an error occurs. + * + * @return the current Baggage. + */ + public static Baggage fromContext(final Context context) { + try { + return Baggage.fromContext(context); + } catch (final Exception e) { + Logger.error(TAG + ":fromContext", "Failed to get baggage from context", e); + return new NoopBaggage(); + } + } +} diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/NoopBaggage.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/NoopBaggage.java new file mode 100644 index 0000000000..96f8b98cd5 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/NoopBaggage.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package com.microsoft.identity.common.java.opentelemetry; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +import javax.annotation.Nullable; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +import io.opentelemetry.api.baggage.BaggageEntry; + +/** + * A custom noop implementation of {@link Baggage}. + */ +public class NoopBaggage implements Baggage { + + @Override + public int size() { + return 0; + } + + @Override + public void forEach(BiConsumer consumer) {} + + @Override + public Map asMap() { + return new HashMap<>(); + } + + @Nullable + @Override + public String getEntryValue(String entryKey) { + return null; + } + + @Override + public BaggageBuilder toBuilder() { + return null; + } +} diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/OtelContextExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/OtelContextExtension.java index 8c0a9913c8..e9cce3746e 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/OtelContextExtension.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/OtelContextExtension.java @@ -18,11 +18,14 @@ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CO +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package com.microsoft.identity.common.java.opentelemetry; import com.microsoft.identity.common.java.logging.Logger; +import javax.annotation.Nullable; + import io.opentelemetry.context.Context; /** @@ -65,4 +68,23 @@ public static Runnable wrap(final Runnable runnable) { } } + /** + * Returns the current context, or a root context if an error occurs. + * See {@link Context#current()} + * + * @return the current context, or null if not available. + */ + @Nullable + public static Context current() { + try { + return Context.current(); + } catch (final NoSuchMethodError error) { + Logger.error( + TAG + ":current", + error.getMessage(), + error + ); + return null; + } + } } diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanExtension.java index 71ffa2fbf1..31f2e388d6 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanExtension.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanExtension.java @@ -28,6 +28,7 @@ import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanId; import io.opentelemetry.api.trace.TraceId; +import io.opentelemetry.context.Context; import io.opentelemetry.context.ImplicitContextKeyed; import io.opentelemetry.context.Scope; import lombok.NonNull; @@ -90,6 +91,21 @@ public static Scope makeCurrentSpan(@NonNull final Span span) { } } + /** + * A safe implementation of {@link Span#fromContext(Context)} that doesn't crash. + * + * @param context the {@link Context} from which to extract the {@link Span} + * @return a {@link Span} + */ + public static Span fromContext(@NonNull final Context context) { + try { + return Span.fromContext(context); + } catch (final Exception error) { + Logger.error(TAG + ":fromContext", error.getMessage(), error); + return new NoopSpan(INVALID); + } + } + /** * This is a custom No-op implementation of {@link Scope}. This should be viewed the same as the * default Noop implementation in {@link io.opentelemetry.context.ThreadLocalContextStorage}. diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java index e1eee4b6dd..485bf593a6 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java @@ -22,9 +22,13 @@ // THE SOFTWARE. package com.microsoft.identity.common.java.opentelemetry; +import com.microsoft.identity.common.java.logging.Logger; + import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; + import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; import io.opentelemetry.context.Context; @@ -38,10 +42,7 @@ */ public final class TextMapPropagatorExtension { - private static final TextMapPropagator PROPAGATOR = TextMapPropagator.composite( - W3CTraceContextPropagator.getInstance(), - W3CBaggagePropagator.getInstance() - ); + private static final String TAG = TextMapPropagatorExtension.class.getSimpleName(); /** * Private constructor to prevent instantiation. @@ -56,46 +57,67 @@ private TextMapPropagatorExtension() { * @param context The context to inject. If null, the current context will be used. * @return A map containing the injected context properties. */ - public static HashMap inject(Context context) { - final HashMap carrier = new HashMap<>(); - final Context contextToInject = context != null ? context : Context.current(); + public static HashMap inject(final Context context) { + try { + final HashMap carrier = new HashMap<>(); + final Context contextToInject = context != null ? context : Context.current(); - final TextMapSetter> setter = new TextMapSetter>() { - @Override - public void set(Map carrier, String key, String value) { - if (carrier != null && key != null && value != null) { - carrier.put(key, value); + final TextMapSetter> setter = new TextMapSetter>() { + @Override + public void set(Map carrier, String key, String value) { + if (carrier != null && key != null && value != null) { + carrier.put(key, value); + } } - } - }; + }; - PROPAGATOR.inject(contextToInject, carrier, setter); - return carrier; + final TextMapPropagator propagator = TextMapPropagator.composite( + W3CTraceContextPropagator.getInstance(), + W3CBaggagePropagator.getInstance() + ); + propagator.inject(contextToInject, carrier, setter); + return carrier; + } catch (final Exception e) { + // Log the error and return an empty map if injection fails + Logger.error(TAG + ":inject", "Failed to inject context", e); + return new HashMap<>(); + } } /** * Extracts context from a carrier map. * * @param carrier The carrier containing context information. - * @return The extracted context, or the current context if extraction fails. + * @return The extracted context, or null if extraction fails. */ - public static Context extract(Map carrier) { - if (carrier == null || carrier.isEmpty()) { - return Context.current(); - } - - final TextMapGetter> getter = new TextMapGetter>() { - @Override - public String get(Map carrier, String key) { - return carrier.get(key); + @Nullable + public static Context extract(final Map carrier) { + try { + if (carrier == null || carrier.isEmpty()) { + return Context.current(); } - @Override - public Iterable keys(Map carrier) { - return carrier.keySet(); - } - }; + final TextMapGetter> getter = new TextMapGetter>() { + @Override + public String get(final Map carrier, final String key) { + return carrier.get(key); + } - return PROPAGATOR.extract(Context.current(), carrier, getter); + @Override + public Iterable keys(final Map carrier) { + return carrier.keySet(); + } + }; + + final TextMapPropagator propagator = TextMapPropagator.composite( + W3CTraceContextPropagator.getInstance(), + W3CBaggagePropagator.getInstance() + ); + return propagator.extract(Context.current(), carrier, getter); + } catch (final Exception e) { + // Log the error and return null if extraction fails + Logger.error(TAG + ":extract", "Failed to extract context", e); + return null; + } } } From c20b5a60cff4877ce17180aa0b7f47d04cb5784d Mon Sep 17 00:00:00 2001 From: Melissa Ahn Date: Sun, 29 Jun 2025 17:55:03 -0700 Subject: [PATCH 7/9] final --- .../common/java/opentelemetry/TextMapPropagatorExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java index 485bf593a6..de5215e2bb 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java @@ -64,7 +64,7 @@ public static HashMap inject(final Context context) { final TextMapSetter> setter = new TextMapSetter>() { @Override - public void set(Map carrier, String key, String value) { + public void set(final Map carrier, final String key, final String value) { if (carrier != null && key != null && value != null) { carrier.put(key, value); } From c6b72ede45f2d5cb083cb5d45e49428c1cc69fc7 Mon Sep 17 00:00:00 2001 From: Melissa Ahn Date: Sun, 29 Jun 2025 19:28:43 -0700 Subject: [PATCH 8/9] took suggestions --- .../internal/fido/AuthFidoChallengeHandler.kt | 14 ++++++----- .../java/opentelemetry/NoopBaggage.java | 25 +++++++++++++++++-- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt b/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt index deb68e9160..efe13992c3 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt +++ b/common/src/main/java/com/microsoft/identity/common/internal/fido/AuthFidoChallengeHandler.kt @@ -57,12 +57,14 @@ class AuthFidoChallengeHandler ( private val lifecycleOwner: LifecycleOwner? ) : IChallengeHandler { val TAG = AuthFidoChallengeHandler::class.simpleName.toString() - private val parentAttributeNames = arrayListOf( - AttributeName.correlation_id, - AttributeName.tenant_id, - AttributeName.account_type, - AttributeName.calling_package_name - ) + companion object { + private val parentAttributeNames = arrayListOf( + AttributeName.correlation_id, + AttributeName.tenant_id, + AttributeName.account_type, + AttributeName.calling_package_name + ) + } override fun processChallenge(fidoChallenge: FidoChallenge): Void? { val methodTag = "$TAG:processChallenge" diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/NoopBaggage.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/NoopBaggage.java index 96f8b98cd5..1ce6a51703 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/NoopBaggage.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/NoopBaggage.java @@ -31,6 +31,7 @@ import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.baggage.BaggageBuilder; import io.opentelemetry.api.baggage.BaggageEntry; +import io.opentelemetry.api.baggage.BaggageEntryMetadata; /** * A custom noop implementation of {@link Baggage}. @@ -52,12 +53,32 @@ public Map asMap() { @Nullable @Override - public String getEntryValue(String entryKey) { + public String getEntryValue(final String entryKey) { return null; } @Override public BaggageBuilder toBuilder() { - return null; + return new NoopBaggageBuilder(); + } + /** + * A no-op implementation of {@link BaggageBuilder}. + */ + private static class NoopBaggageBuilder implements BaggageBuilder { + + @Override + public BaggageBuilder put(final String key, final String value, final BaggageEntryMetadata entryMetadata) { + return this; + } + + @Override + public BaggageBuilder remove(final String key) { + return this; + } + + @Override + public Baggage build() { + return new NoopBaggage(); + } } } From 2b5133a9c7d1f576ff941effef4bb89d5d28b278 Mon Sep 17 00:00:00 2001 From: Melissa Ahn Date: Wed, 23 Jul 2025 21:01:13 -0700 Subject: [PATCH 9/9] modifier and flight --- .../internal/providers/oauth2/AuthorizationActivity.java | 2 +- .../java/opentelemetry/TextMapPropagatorExtension.java | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java index a6547af516..9decdb25e6 100644 --- a/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java +++ b/common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivity.java @@ -85,7 +85,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { mOtelContext = TextMapPropagatorExtension.extract(carrier); } catch (final TerminalException | ClassCastException | NullPointerException e) { // Don't want to block any features if an error occurs during deserialization. - Logger.warn(methodTag, "Exception thrown during extraction: " + e.getMessage()); + Logger.error(methodTag, "Exception thrown during extraction: " + e.getMessage(), e); } } final Fragment fragment = AuthorizationActivityFactory.getAuthorizationFragmentFromStartIntent(getIntent()); diff --git a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java index de5215e2bb..546eb4f7fe 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java +++ b/common4j/src/main/com/microsoft/identity/common/java/opentelemetry/TextMapPropagatorExtension.java @@ -35,22 +35,17 @@ import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapSetter; +import lombok.experimental.UtilityClass; /** * Extension class for handling OpenTelemetry context propagation. * Provides utility methods for injecting and extracting context which contains Baggage and SpanContext. */ +@UtilityClass public final class TextMapPropagatorExtension { private static final String TAG = TextMapPropagatorExtension.class.getSimpleName(); - /** - * Private constructor to prevent instantiation. - */ - private TextMapPropagatorExtension() { - // This utility class is not meant to be instantiated - } - /** * Injects the current context into a carrier. *