Skip to content
Merged
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Version 21.3.0
- [MINOR] changes accommodating the WrappedKeyAlgoIdentifier module (#2667)
- [MINOR] Fixing the sign in screens when edge to edge is enabled (#2665)
- [MINOR] Native auth: Make native auth MFA feature more backward compatible(#2669)
- [MINOR] Using Baggage to propagate attributes from parent Span (##2671)
- [MINOR] Showing webcp flow in webview (#2673)

Version 21.2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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<FidoChallenge, Void> {
val TAG = AuthFidoChallengeHandler::class.simpleName.toString()
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"
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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() {
Expand All @@ -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<String, String> 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<String, String> temp = getIntent().getExtras() != null ?
(Map<String, String>) 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -118,6 +121,10 @@ object AuthorizationActivityFactory {
.build()
)
)
putExtra(
OTEL_CONTEXT_CARRIER,
TextMapPropagatorExtension.inject(Context.current())
)
if (parameters.sourceLibraryName != null) {
putExtra(PRODUCT, parameters.sourceLibraryName)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,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;

/**
Expand Down Expand Up @@ -261,7 +262,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 =
Expand All @@ -277,7 +278,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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class AuthFidoChallengeHandlerTest {
authFidoChallengeHandler = AuthFidoChallengeHandler(
fidoManager = testFidoManager,
webView = webView,
spanContext = null,
oTelContext = null,
lifecycleOwner = testLifecycleOwner
)
}
Expand Down Expand Up @@ -126,7 +126,7 @@ class AuthFidoChallengeHandlerTest {
authFidoChallengeHandler = AuthFidoChallengeHandler(
fidoManager = testFidoManager,
webView = webView,
spanContext = null,
oTelContext = null,
lifecycleOwner = null
)
assertFalse(webView.urlLoaded)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,29 @@ public enum AttributeName {
*/
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,

/**
* Records if the request is a webcp authorize request.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 which contains Baggage and SpanContext.
*/
public final class TextMapPropagatorExtension {

private static final TextMapPropagator PROPAGATOR = TextMapPropagator.composite(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you check this (and any new otel code introduced in this PR i.e. methods we were previously not using) if they need to wrapped in a try/catch block to make it safe for older devices. This is what the other "extension" classes written for otel are doing to protect against exceptions that fall into the MethodNotAvailable sort of category on older devices.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted; I added try/catch blocks in the methods of the new extension classes I added, as well as new methods I added to existing extension classes.

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.
*
* @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<String, String> inject(Context context) {
final HashMap<String, String> carrier = new HashMap<>();
final Context contextToInject = context != null ? context : Context.current();

final TextMapSetter<Map<String, String>> setter = new TextMapSetter<Map<String, String>>() {
@Override
public void set(Map<String, String> 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.
*
* @param carrier The carrier containing context information.
* @return The extracted context, or the current context if extraction fails.
*/
public static Context extract(Map<String, String> carrier) {
if (carrier == null || carrier.isEmpty()) {
return Context.current();
}

final TextMapGetter<Map<String, String>> getter = new TextMapGetter<Map<String, String>>() {
@Override
public String get(Map<String, String> carrier, String key) {
return carrier.get(key);
}

@Override
public Iterable<String> keys(Map<String, String> carrier) {
return carrier.keySet();
}
};

return PROPAGATOR.extract(Context.current(), carrier, getter);
}
}
Loading