Skip to content

Commit 43cf249

Browse files
committed
Revert "Add OpenTelemetry support for passkey operations, Fixes AB#3412004 (AzureAD#2795)"
This reverts commit 8d611f4.
1 parent 4c96a50 commit 43cf249

File tree

8 files changed

+77
-116
lines changed

8 files changed

+77
-116
lines changed

common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/PasskeyReplyChannel.kt

Lines changed: 39 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,9 @@ import androidx.credentials.exceptions.GetCredentialProviderConfigurationExcepti
3333
import androidx.credentials.exceptions.GetCredentialUnknownException
3434
import androidx.credentials.exceptions.NoCredentialException
3535
import androidx.webkit.JavaScriptReplyProxy
36-
import com.microsoft.identity.common.java.opentelemetry.AttributeName
37-
import com.microsoft.identity.common.java.opentelemetry.OTelUtility
38-
import com.microsoft.identity.common.java.opentelemetry.SpanExtension
39-
import com.microsoft.identity.common.java.opentelemetry.SpanName
4036
import com.microsoft.identity.common.logging.Logger
41-
import io.opentelemetry.api.trace.SpanContext
42-
import io.opentelemetry.api.trace.StatusCode
4337
import org.json.JSONObject
38+
import kotlin.jvm.Throws
4439

4540

4641
/**
@@ -53,8 +48,7 @@ import org.json.JSONObject
5348
*/
5449
class PasskeyReplyChannel(
5550
private val replyProxy: JavaScriptReplyProxy,
56-
private val requestType: String = "unknown",
57-
private val spanContext: SpanContext? = null
51+
private val requestType: String = "unknown"
5852
) {
5953
companion object {
6054
const val TAG = "PasskeyReplyChannel"
@@ -84,12 +78,8 @@ class PasskeyReplyChannel(
8478
* Sealed class representing messages sent to JavaScript.
8579
*/
8680
sealed class ReplyMessage {
87-
// Message type (e.g., "create", "get").
8881
abstract val type: String
89-
// Message status ("success" or "error").
9082
abstract val status: String
91-
// Message data as a JSON object.
92-
// Either credential data for success or {domExceptionMessage, domExceptionName} for error.
9383
abstract val data: JSONObject
9484

9585
/**
@@ -112,8 +102,8 @@ class PasskeyReplyChannel(
112102
* @property type Request type that failed.
113103
*/
114104
class Error(
115-
val domExceptionMessage: String,
116-
val domExceptionName: String = DOM_EXCEPTION_NOT_ALLOWED_ERROR,
105+
private val domExceptionMessage: String,
106+
private val domExceptionName: String = DOM_EXCEPTION_NOT_ALLOWED_ERROR,
117107
override val type: String
118108
) : ReplyMessage() {
119109
override val status = ERROR_STATUS
@@ -141,35 +131,22 @@ class PasskeyReplyChannel(
141131
*
142132
* @param json JSON string containing the credential response.
143133
*/
144-
@SuppressLint("RequiresFeature", "Only called when feature is available")
145134
fun postSuccess(json: String) {
146135
val methodTag = "$TAG:postSuccess"
147-
val span = OTelUtility.createSpanFromParent(
148-
SpanName.PasskeyWebListener.name,
149-
spanContext
150-
)
151-
152-
try {
153-
SpanExtension.makeCurrentSpan(span).use {
154-
val successMessage = ReplyMessage.Success(json, requestType).toString()
155-
replyProxy.postMessage(successMessage)
156-
Logger.info(methodTag, "RequestType: $requestType was successful.")
157-
span.setAttribute(AttributeName.passkey_operation_type.name, requestType)
158-
span.setStatus(StatusCode.OK)
159-
}
160-
} catch (throwable: Throwable) {
161-
span.setStatus(StatusCode.ERROR)
162-
span.setAttribute(AttributeName.passkey_operation_type.name, requestType)
163-
span.recordException(throwable)
164-
Logger.error(methodTag, "Reply message failed", throwable)
165-
throw throwable
166-
} finally {
167-
span.end()
168-
}
136+
send(ReplyMessage.Success(json, requestType))
137+
Logger.info(methodTag, "RequestType: $requestType, was successful.")
169138
}
170139

171-
172-
140+
/**
141+
* Posts an error message with a custom error description.
142+
*
143+
* @param errorMessage Error description to send.
144+
*/
145+
fun postError(errorMessage: String) {
146+
postErrorInternal(
147+
ReplyMessage.Error(domExceptionMessage = errorMessage, type = requestType)
148+
)
149+
}
173150

174151
/**
175152
* Posts an error message based on a thrown exception.
@@ -178,33 +155,17 @@ class PasskeyReplyChannel(
178155
*
179156
* @param throwable Exception to convert and send.
180157
*/
181-
@SuppressLint("RequiresFeature", "Only called when feature is available")
182158
fun postError(throwable: Throwable) {
183-
val methodTag = "$TAG:postError"
184-
val span = OTelUtility.createSpanFromParent(
185-
SpanName.PasskeyWebListener.name,
186-
spanContext
187-
)
159+
postErrorInternal(throwableToErrorMessage(throwable))
160+
}
188161

189-
try {
190-
SpanExtension.makeCurrentSpan(span).use {
191-
val errorMessage = throwableToErrorMessage(throwable)
192-
replyProxy.postMessage(errorMessage.toString())
193-
span.setAttribute(AttributeName.passkey_operation_type.name, requestType)
194-
span.setAttribute(AttributeName.passkey_dom_exception_name.name, errorMessage.domExceptionName)
195-
span.setStatus(StatusCode.ERROR)
196-
span.recordException(throwable)
197-
Logger.error(methodTag, "RequestType: $requestType failed with error: $errorMessage", null)
198-
}
199-
} catch (unexpectedException: Throwable) {
200-
span.setStatus(StatusCode.ERROR)
201-
span.recordException(unexpectedException)
202-
span.setAttribute(AttributeName.passkey_operation_type.name, requestType)
203-
Logger.error(methodTag, "Reply message failed", unexpectedException)
204-
throw unexpectedException
205-
} finally {
206-
span.end() // Always end the span
207-
}
162+
/**
163+
* Internal method to send error messages and log them.
164+
*/
165+
private fun postErrorInternal(errorMessage: ReplyMessage.Error) {
166+
val methodTag = "$TAG:postError"
167+
send(errorMessage)
168+
Logger.error(methodTag, "RequestType: $requestType, failed with error: $errorMessage", null)
208169
}
209170

210171
/**
@@ -250,4 +211,18 @@ class PasskeyReplyChannel(
250211
type = requestType
251212
)
252213
}
214+
215+
/**
216+
* Sends a message to JavaScript via the reply proxy.
217+
*/
218+
@SuppressLint("RequiresFeature", "Only called when feature is available")
219+
private fun send(message: ReplyMessage) {
220+
val methodTag = "$TAG:send"
221+
try {
222+
replyProxy.postMessage(message.toString())
223+
} catch (t: Throwable) {
224+
Logger.error(methodTag, "Reply message failed", t)
225+
throw t
226+
}
227+
}
253228
}

common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/PasskeyWebListener.kt

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,24 +110,14 @@ class PasskeyWebListener(
110110

111111
// Only allow one request at a time.
112112
if (havePendingRequest.get()) {
113-
passkeyReplyChannel.postError(
114-
ClientException(
115-
ClientException.REQUEST_IN_PROGRESS,
116-
"A WebAuthN request is already in progress."
117-
)
118-
)
113+
passkeyReplyChannel.postError("Request already in progress")
119114
return
120115
}
121116
havePendingRequest.set(true)
122117

123118
// Only allow requests from the main frame.
124119
if (!isMainFrame) {
125-
passkeyReplyChannel.postError(
126-
ClientException(
127-
ClientException.UNSUPPORTED_OPERATION,
128-
"WebAuthN requests from iframes are not supported."
129-
)
130-
)
120+
passkeyReplyChannel.postError("Requests from iframes are not supported")
131121
havePendingRequest.set(false)
132122
return
133123
}
@@ -153,12 +143,7 @@ class PasskeyWebListener(
153143
}
154144

155145
else -> {
156-
passkeyReplyChannel.postError(
157-
ClientException(
158-
ClientException.UNSUPPORTED_OPERATION,
159-
"Unsupported WebAuthN request type: ${webAuthNMessage.type}"
160-
)
161-
)
146+
passkeyReplyChannel.postError("Unknown request type: ${webAuthNMessage.type}")
162147
havePendingRequest.set(false)
163148
}
164149
}
@@ -182,12 +167,8 @@ class PasskeyWebListener(
182167
if (publicKeyCredential != null) {
183168
reply.postSuccess(publicKeyCredential.authenticationResponseJson)
184169
} else {
185-
reply.postError(
186-
ClientException(
187-
ClientException.UNSUPPORTED_OPERATION,
188-
"Retrieved credential is not a PublicKeyCredential."
189-
)
190-
) }
170+
reply.postError("Unexpected credential type: ${credentialResponse.credential.javaClass.name}")
171+
}
191172
}
192173
.onFailure { throwable ->
193174
reply.postError(throwable)

common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ public class AzureActiveDirectoryWebViewClient extends OAuth2WebViewClient {
141141
private boolean mAuthUxJavaScriptInterfaceAdded = false;
142142
// Determines whether to handle WebCP requests in the WebView in brokerless scenarios.
143143
private final boolean mIsWebViewWebCpEnabledInBrokerlessCase;
144-
private final SpanContext mSpanContext;
144+
145+
145146
private final String mUtid;
146147

147148
private final List<JsScriptRecord> mOnPageStartedScripts = new ArrayList<>();
@@ -158,7 +159,6 @@ public AzureActiveDirectoryWebViewClient(@NonNull final Activity activity,
158159
mCertBasedAuthFactory = new CertBasedAuthFactory(activity);
159160
mSwitchBrowserRequestHandler = switchBrowserRequestHandler;
160161
mUtid = utid;
161-
mSpanContext = activity instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
162162
mIsWebViewWebCpEnabledInBrokerlessCase = isWebViewWebCpEnabledInBrokerlessCase;
163163
}
164164

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

1064-
final Span span = OTelUtility.createSpanFromParent(SpanName.ProcessCrossCloudRedirect.name(), mSpanContext);
1066+
final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
1067+
final Span span = spanContext != null ?
1068+
OTelUtility.createSpanFromParent(SpanName.ProcessCrossCloudRedirect.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessCrossCloudRedirect.name());
10651069
final ReAttachPrtHeaderHandler reAttachPrtHeaderHandler = new ReAttachPrtHeaderHandler(view, mRequestHeaders, span);
10661070
reAttachPrtHeader(url, reAttachPrtHeaderHandler, view, methodTag, span);
10671071
}
@@ -1208,7 +1212,9 @@ private String getBrokerAppPackageNameFromUrl(@NonNull final String url) {
12081212
* @return Created {@link Span}
12091213
*/
12101214
private Span createSpanWithAttributesFromParent(@NonNull final String spanName) {
1211-
final Span span = OTelUtility.createSpanFromParent(spanName, mSpanContext);
1215+
final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
1216+
final Span span = spanContext != null ?
1217+
OTelUtility.createSpanFromParent(spanName, spanContext) : OTelUtility.createSpan(spanName);
12121218
if (mUtid != null) {
12131219
span.setAttribute(AttributeName.tenant_id.name(), mUtid);
12141220
}

common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/PasskeyReplyChannelTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,26 @@ class PasskeyReplyChannelTest {
8787
assertEquals(0, dataObject.length())
8888
}
8989

90+
@Test
91+
fun `postError with string sends correct error format`() {
92+
// Given
93+
val errorMessage = "Test error message"
94+
val messageSlot = slot<String>()
95+
96+
// When
97+
passkeyReplyChannel.postError(errorMessage)
98+
99+
// Then
100+
verify { mockReplyProxy.postMessage(capture(messageSlot)) }
101+
102+
val messageObject = JSONObject(messageSlot.captured)
103+
val dataObject = messageObject.getJSONObject(PasskeyReplyChannel.DATA_KEY)
104+
105+
assertEquals(errorMessage, dataObject.getString(PasskeyReplyChannel.DOM_EXCEPTION_MESSAGE_KEY))
106+
assertEquals(PasskeyReplyChannel.DOM_EXCEPTION_NOT_ALLOWED_ERROR,
107+
dataObject.getString(PasskeyReplyChannel.DOM_EXCEPTION_NAME_KEY))
108+
}
109+
90110
@Test
91111
fun `postError with cancellation exception returns NotAllowedError`() {
92112
// Given

common/src/test/java/com/microsoft/identity/common/internal/providers/oauth2/PasskeyWebListenerTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class PasskeyWebListenerTest {
304304
val responseObject = JSONObject(messageSlot.captured)
305305
assertEquals(PasskeyReplyChannel.ERROR_STATUS, responseObject.getString(PasskeyReplyChannel.STATUS_KEY))
306306
val dataObject = responseObject.getJSONObject(PasskeyReplyChannel.DATA_KEY)
307-
assertTrue(dataObject.getString(PasskeyReplyChannel.DOM_EXCEPTION_MESSAGE_KEY).contains("Unsupported WebAuthN request type: unknown_type"))
307+
assertTrue(dataObject.getString(PasskeyReplyChannel.DOM_EXCEPTION_MESSAGE_KEY).contains("Unknown request type"))
308308
}
309309

310310
// ========== Frame Origin Tests ==========

common4j/src/main/com/microsoft/identity/common/java/exception/ClientException.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,6 @@ public class ClientException extends BaseException {
133133
*/
134134
public static final String UNSUPPORTED_ENCODING = "unsupported_encoding";
135135

136-
/**
137-
* The operation is not supported.
138-
*/
139-
public static final String UNSUPPORTED_OPERATION = "unsupported_operation";
140-
141-
/**
142-
* The request is already in progress.
143-
*/
144-
public static final String REQUEST_IN_PROGRESS = "request_in_progress";
145-
146136
/**
147137
* The designated crypto alg is not supported.
148138
*/

common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -487,15 +487,5 @@ public enum AttributeName {
487487
/**
488488
* Records if current flow is in webcp flow.
489489
*/
490-
is_in_web_cp_flow,
491-
492-
/**
493-
* Passkey operation type (e.g., registration, authentication).
494-
*/
495-
passkey_operation_type,
496-
497-
/**
498-
* Passkey DOM exception name (if any).
499-
*/
500-
passkey_dom_exception_name,
490+
is_in_web_cp_flow
501491
}

common4j/src/main/com/microsoft/identity/common/java/opentelemetry/SpanName.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ public enum SpanName {
7272
GetAllSsoTokens,
7373
ProcessWebCpEnrollmentRedirect,
7474
ProcessWebCpAuthorizeUrlRedirect,
75-
PasskeyWebListener,
7675
PersistToStorageAsync,
7776
InstallCertOnWpj
7877
}

0 commit comments

Comments
 (0)