Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import androidx.credentials.exceptions.GetCredentialProviderConfigurationExcepti
import androidx.credentials.exceptions.GetCredentialUnknownException
import androidx.credentials.exceptions.NoCredentialException
import androidx.webkit.JavaScriptReplyProxy
import com.microsoft.identity.common.java.opentelemetry.AttributeName
import com.microsoft.identity.common.java.opentelemetry.SpanExtension
import com.microsoft.identity.common.logging.Logger
import io.opentelemetry.api.trace.StatusCode
import org.json.JSONObject
import kotlin.jvm.Throws

Expand Down Expand Up @@ -78,8 +81,12 @@ class PasskeyReplyChannel(
* Sealed class representing messages sent to JavaScript.
*/
sealed class ReplyMessage {
// Message type (e.g., "create", "get").
abstract val type: String
// Message status ("success" or "error").
abstract val status: String
// Message data as a JSON object.
// Either credential data for success or {domExceptionMessage, domExceptionName} for error.
abstract val data: JSONObject

/**
Expand All @@ -102,8 +109,8 @@ class PasskeyReplyChannel(
* @property type Request type that failed.
*/
class Error(
private val domExceptionMessage: String,
private val domExceptionName: String = DOM_EXCEPTION_NOT_ALLOWED_ERROR,
val domExceptionMessage: String,
val domExceptionName: String = DOM_EXCEPTION_NOT_ALLOWED_ERROR,
override val type: String
) : ReplyMessage() {
override val status = ERROR_STATUS
Expand Down Expand Up @@ -134,6 +141,11 @@ class PasskeyReplyChannel(
fun postSuccess(json: String) {
val methodTag = "$TAG:postSuccess"
send(ReplyMessage.Success(json, requestType))
SpanExtension.current().apply {
setStatus(StatusCode.OK)
setAttribute(AttributeName.passkey_operation_type.name, requestType)
end()
}
Logger.info(methodTag, "RequestType: $requestType, was successful.")
}

Expand All @@ -146,6 +158,12 @@ class PasskeyReplyChannel(
postErrorInternal(
ReplyMessage.Error(domExceptionMessage = errorMessage, type = requestType)
)
SpanExtension.current().apply {
setAttribute(AttributeName.passkey_operation_type.name, requestType)
setStatus(StatusCode.ERROR)
setAttribute(AttributeName.error_message.name, errorMessage)
end()
}
}

/**
Expand All @@ -157,6 +175,12 @@ class PasskeyReplyChannel(
*/
fun postError(throwable: Throwable) {
postErrorInternal(throwableToErrorMessage(throwable))
SpanExtension.current().apply {
setAttribute(AttributeName.passkey_operation_type.name, requestType)
setStatus(StatusCode.ERROR)
recordException(throwable)
end()
}
}

/**
Expand Down Expand Up @@ -220,9 +244,15 @@ class PasskeyReplyChannel(
val methodTag = "$TAG:send"
try {
replyProxy.postMessage(message.toString())
} catch (t: Throwable) {
Logger.error(methodTag, "Reply message failed", t)
throw t
} catch (throwable: Throwable) {
SpanExtension.current().apply {
setStatus(StatusCode.ERROR)
setAttribute(AttributeName.passkey_operation_type.name, requestType)
recordException(throwable)
end()
}
Logger.error(methodTag, "Reply message failed", throwable)
throw throwable
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ import androidx.webkit.WebViewFeature
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.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.trace.Span
import io.opentelemetry.api.trace.SpanContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand All @@ -55,8 +60,13 @@ import java.util.concurrent.atomic.AtomicBoolean
class PasskeyWebListener(
private val coroutineScope: CoroutineScope,
private val credentialManagerHandler: CredentialManagerHandler,
spanContext: SpanContext? = null
) : WebViewCompat.WebMessageListener {

val span: Span by lazy {
OTelUtility.createSpanFromParent(SpanName.PasskeyWebListener.name, spanContext)
}

/** Tracks if a WebAuthN request is currently pending. Only one request is allowed at a time. */
private val havePendingRequest = AtomicBoolean(false)

Expand All @@ -77,6 +87,7 @@ class PasskeyWebListener(
isMainFrame: Boolean,
replyProxy: JavaScriptReplyProxy,
) {
SpanExtension.makeCurrentSpan(span)
parseMessage(message.data, replyProxy)?.let { webAuthNMessage ->
onRequest(
webAuthNMessage = webAuthNMessage,
Expand Down Expand Up @@ -212,16 +223,25 @@ class PasskeyWebListener(
val passkeyReplyChannel = PasskeyReplyChannel(javaScriptReplyProxy)
return runCatching {
if (messageData.isNullOrBlank()) {
throw ClientException(ClientException.MISSING_PARAMETER, "Message data is null or blank")
throw ClientException(
ClientException.MISSING_PARAMETER,
"Message data is null or blank"
)
}
val json = JSONObject(messageData)
val type = json.optString(TYPE_KEY).takeIf { it.isNotBlank() }
val request = json.optString(REQUEST_KEY).takeIf { it.isNotBlank() }

if (type == null) {
throw ClientException(ClientException.MISSING_PARAMETER, "Missing required key: type")
throw ClientException(
ClientException.MISSING_PARAMETER,
"Missing required key: type"
)
} else if (request == null) {
throw ClientException(ClientException.MISSING_PARAMETER, "Missing required key: request")
throw ClientException(
ClientException.MISSING_PARAMETER,
"Missing required key: request"
)
} else {
WebAuthNMessage(type, request)
}
Expand Down Expand Up @@ -341,7 +361,8 @@ class PasskeyWebListener(
getAllowedOriginRules(),
PasskeyWebListener(
coroutineScope = CoroutineScope(Dispatchers.Default),
credentialManagerHandler = CredentialManagerHandler(activity)
credentialManagerHandler = CredentialManagerHandler(activity),
spanContext = webClient.spanContext
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ public class AzureActiveDirectoryWebViewClient extends OAuth2WebViewClient {

private final List<JsScriptRecord> mOnPageStartedScripts = new ArrayList<>();

private final SpanContext mSpanContext;

public AzureActiveDirectoryWebViewClient(@NonNull final Activity activity,
@NonNull final IAuthorizationCompletionCallback completionCallback,
@NonNull final OnPageLoadedCallback pageLoadedCallback,
Expand All @@ -159,6 +161,7 @@ public AzureActiveDirectoryWebViewClient(@NonNull final Activity activity,
mCertBasedAuthFactory = new CertBasedAuthFactory(activity);
mSwitchBrowserRequestHandler = switchBrowserRequestHandler;
mUtid = utid;
mSpanContext = activity instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
mIsWebViewWebCpEnabledInBrokerlessCase = isWebViewWebCpEnabledInBrokerlessCase;
}

Expand Down Expand Up @@ -1022,9 +1025,7 @@ private void processNonceAndReAttachHeaders(@NonNull final WebView view, @NonNul
AttributeName.is_sso_nonce_found_in_ests_request.name(), nonceQueryParam != null
);
if (nonceQueryParam != null) {
final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
final Span span = spanContext != null ?
OTelUtility.createSpanFromParent(SpanName.ProcessNonceFromEstsRedirect.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessNonceFromEstsRedirect.name());
final Span span = OTelUtility.createSpanFromParent(SpanName.ProcessNonceFromEstsRedirect.name(), mSpanContext);
try (final Scope scope = SpanExtension.makeCurrentSpan(span)) {
final NonceRedirectHandler nonceRedirect = new NonceRedirectHandler(view, mRequestHeaders, span);
nonceRedirect.processChallenge(new URL(url));
Expand Down Expand Up @@ -1063,9 +1064,7 @@ private void processWebCpAuthorize(@NonNull final WebView view, @NonNull final S
private void processCrossCloudRedirect(@NonNull final WebView view, @NonNull final String url) {
final String methodTag = TAG + ":processCrossCloudRedirect";

final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
final Span span = spanContext != null ?
OTelUtility.createSpanFromParent(SpanName.ProcessCrossCloudRedirect.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessCrossCloudRedirect.name());
final Span span = OTelUtility.createSpanFromParent(SpanName.ProcessCrossCloudRedirect.name(), mSpanContext);
final ReAttachPrtHeaderHandler reAttachPrtHeaderHandler = new ReAttachPrtHeaderHandler(view, mRequestHeaders, span);
reAttachPrtHeader(url, reAttachPrtHeaderHandler, view, methodTag, span);
}
Expand Down Expand Up @@ -1212,9 +1211,7 @@ private String getBrokerAppPackageNameFromUrl(@NonNull final String url) {
* @return Created {@link Span}
*/
private Span createSpanWithAttributesFromParent(@NonNull final String spanName) {
final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
final Span span = spanContext != null ?
OTelUtility.createSpanFromParent(spanName, spanContext) : OTelUtility.createSpan(spanName);
final Span span = OTelUtility.createSpanFromParent(spanName, mSpanContext);
if (mUtid != null) {
span.setAttribute(AttributeName.tenant_id.name(), mUtid);
}
Expand Down Expand Up @@ -1251,4 +1248,9 @@ public void addOnPageStartedScript(
new JsScriptRecord(scriptId, script, allowedUrls)
);
}

public SpanContext getSpanContext() {
return mSpanContext;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -487,5 +487,10 @@ public enum AttributeName {
/**
* Records if current flow is in webcp flow.
*/
is_in_web_cp_flow
is_in_web_cp_flow,

/**
* Passkey operation type (e.g., registration, authentication).
*/
passkey_operation_type
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public enum SpanName {
GetAllSsoTokens,
ProcessWebCpEnrollmentRedirect,
ProcessWebCpAuthorizeUrlRedirect,
PasskeyWebListener,
PersistToStorageAsync,
InstallCertOnWpj
}
Loading