From d98d3e1f924f79d7980d520309a220b99b6186b4 Mon Sep 17 00:00:00 2001 From: fadidurah Date: Thu, 14 May 2026 03:03:47 -0400 Subject: [PATCH 1/6] Build: publish libraries directly to NewAndroid feed Switches Maven publishing from the AndroidADAL feed to the NewAndroid feed to eliminate the upstream-feed traversal latency that occurs when downstream pipeline stages resolve freshly-published artifacts. Background: - Builds publish to AndroidADAL but consumers fetch from NewAndroid. - NewAndroid resolves AndroidADAL only via its upstream chain, after walking Maven Central, Google, etc., causing first-fetch latency on every newly published version (which CI agents always pay due to ephemeral caches). - AndroidADAL will be added as an upstream of NewAndroid (done outside this change) so any external consumers still resolving from AndroidADAL keep working transparently. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- LabApiUtilities/build.gradle | 2 +- common/build.gradle | 2 +- common4j/build.gradle | 2 +- keyvault/build.gradle | 2 +- labapi/build.gradle | 2 +- testutils/build.gradle | 2 +- uiautomationutilities/build.gradle | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LabApiUtilities/build.gradle b/LabApiUtilities/build.gradle index b991327e96..790d6b0601 100644 --- a/LabApiUtilities/build.gradle +++ b/LabApiUtilities/build.gradle @@ -114,7 +114,7 @@ publishing { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/common/build.gradle b/common/build.gradle index 8ef607f715..5da8792b1f 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -420,7 +420,7 @@ afterEvaluate { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" credentials { username project.vstsUsername password project.vstsMavenAccessToken diff --git a/common4j/build.gradle b/common4j/build.gradle index 60dc352ecf..4a17ca2aea 100644 --- a/common4j/build.gradle +++ b/common4j/build.gradle @@ -197,7 +197,7 @@ publishing { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/keyvault/build.gradle b/keyvault/build.gradle index ace7611de8..32ff9cab88 100644 --- a/keyvault/build.gradle +++ b/keyvault/build.gradle @@ -29,7 +29,7 @@ publishing { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/labapi/build.gradle b/labapi/build.gradle index c7be9b067b..58d2d923cc 100644 --- a/labapi/build.gradle +++ b/labapi/build.gradle @@ -30,7 +30,7 @@ publishing { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/testutils/build.gradle b/testutils/build.gradle index f03c50da2e..ba77838c57 100644 --- a/testutils/build.gradle +++ b/testutils/build.gradle @@ -129,7 +129,7 @@ project.afterEvaluate{ repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/uiautomationutilities/build.gradle b/uiautomationutilities/build.gradle index 0dabacbe89..897c14dedd 100644 --- a/uiautomationutilities/build.gradle +++ b/uiautomationutilities/build.gradle @@ -175,7 +175,7 @@ project.afterEvaluate{ repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken From baae2796cc17dde60fbdc0122df76ac8a0a1894a Mon Sep 17 00:00:00 2001 From: fadidurah Date: Thu, 14 May 2026 15:46:16 -0400 Subject: [PATCH 2/6] common4j: don't tie sourcesJar/javadocJar to assemble These tasks are already wired into the publish graph through components.java + the maven-publish plugin, so they will be generated automatically when publish runs. Tying them to assemble made every CI flow that just compiles or runs tests also build javadoc, which is one of the slowest tasks in the common4j build. Local developers who want the artifacts can still run ./gradlew sourcesJar javadocJar explicitly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- common4j/build.gradle | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common4j/build.gradle b/common4j/build.gradle index 4a17ca2aea..716ae1c664 100644 --- a/common4j/build.gradle +++ b/common4j/build.gradle @@ -339,8 +339,11 @@ buildConfig { } afterEvaluate { - assemble.dependsOn sourcesJar, javadocJar - + // sourcesJar and javadocJar are wired into the publish graph via components.java + maven-publish, + // so they will run automatically as part of `publish`. There is no need to also tie them to + // `assemble`, which made every CI build (including test-only / spotbugs-only flows) generate + // javadoc unnecessarily. Generating javadoc for common4j is one of the slower tasks in the build. + // Local devs who explicitly want the artifacts can still run `./gradlew sourcesJar javadocJar`. // these tasks are generated by the buildconfig plugin..for more details, read comment about it // above in the plugins block. compileJava.dependsOn generateBuildConfig, generateTestBuildConfig From 46c3b3110ef4e4e05af90b6be9319c6004c4f7f1 Mon Sep 17 00:00:00 2001 From: fadidurah Date: Thu, 14 May 2026 23:32:00 -0400 Subject: [PATCH 3/6] Add gradle configure-on-demand for build perf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When configure-on-demand is enabled, Gradle only configures the projects that are actually in the dependency graph of the requested tasks. Previously building AADAuthenticator triggered configure of broker4j, brokerHost, java-linux-test-app, LinuxBroker, brokerautomationapp etc. — none of which are needed for AADAuthenticator:assembleDistRelease. Logs showed ~16 min spent in unnecessary configure phases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties b/gradle.properties index ae84629666..f652101c39 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,7 @@ android.useAndroidX=true # https://office.visualstudio.com/Outlook%20Mobile/_wiki/wikis/Outlook-Mobile.wiki/3780/Android-Studio-Gradle-Performance-tips-and-tricks org.gradle.parallel=true org.gradle.daemon=true +org.gradle.configureondemand=true # See https://stackoverflow.com/questions/56075455/expiring-daemon-because-jvm-heap-space-is-exhausted # we must make sure that the total size is <7G, as that's the RAM size of VM on the build pipeline. From d313d386c3046ddc621e16f9df145193fe1b4a45 Mon Sep 17 00:00:00 2001 From: fadidurah Date: Thu, 21 May 2026 12:27:34 -0400 Subject: [PATCH 4/6] Fix POM: write current version for project deps to avoid 'unspecified' The publishing block in testutils and uiautomationutilities iterates implementation dependencies to populate the generated POM. For project deps like project(':keyvault'), it.version reads project(':keyvault').version -- which Gradle defaults to the string 'unspecified' if that sibling project hasn't been configured yet. With Gradle configure-on-demand enabled, the publish task for common may run without configuring sibling projects like :keyvault, :labapi, :common, etc. The published POM then contained 'keyvault:unspecified', which caused downstream consumers (AADAuthenticator distDebugUnitTest) to fail at dependency resolution. Fix: for project dependencies, write the current artifact's version (getAppVersionName()) since all sibling library projects share the same version. External dependencies still use it.version as before. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- testutils/build.gradle | 9 ++++++++- uiautomationutilities/build.gradle | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/testutils/build.gradle b/testutils/build.gradle index ba77838c57..89c8a55e8e 100644 --- a/testutils/build.gradle +++ b/testutils/build.gradle @@ -119,7 +119,14 @@ project.afterEvaluate{ def dependencyNode = dependenciesNode.appendNode('dependency') dependencyNode.appendNode('groupId', it.group) dependencyNode.appendNode('artifactId', it.name) - dependencyNode.appendNode('version', it.version) + // For project dependencies (e.g. project(':keyvault')), it.version + // reads the sibling project's `version` field — which is + // "unspecified" if that project hasn't been configured yet. + // With Gradle configure-on-demand enabled, that's common. + // All sibling library projects share the same version, so write + // the current artifact's version for project deps. + def depVersion = (it instanceof org.gradle.api.artifacts.ProjectDependency) ? getAppVersionName() : it.version + dependencyNode.appendNode('version', depVersion) } } } diff --git a/uiautomationutilities/build.gradle b/uiautomationutilities/build.gradle index 897c14dedd..5266b05c11 100644 --- a/uiautomationutilities/build.gradle +++ b/uiautomationutilities/build.gradle @@ -165,7 +165,14 @@ project.afterEvaluate{ def dependencyNode = dependenciesNode.appendNode('dependency') dependencyNode.appendNode('groupId', it.group) dependencyNode.appendNode('artifactId', it.name) - dependencyNode.appendNode('version', it.version) + // For project dependencies (e.g. project(':keyvault')), it.version + // reads the sibling project's `version` field — which is + // "unspecified" if that project hasn't been configured yet. + // With Gradle configure-on-demand enabled, that's common. + // All sibling library projects share the same version, so write + // the current artifact's version for project deps. + def depVersion = (it instanceof org.gradle.api.artifacts.ProjectDependency) ? getAppVersionName() : it.version + dependencyNode.appendNode('version', depVersion) } } } From c8cd68b6971efba3af604ccf6ff648558140ddb6 Mon Sep 17 00:00:00 2001 From: fadidurah Date: Fri, 22 May 2026 05:08:40 -0400 Subject: [PATCH 5/6] fix token endpoint parsing --- ...tractMicrosoftStsTokenResponseHandler.java | 26 +++++++-- .../MicrosoftStsTokenResponseHandlerTest.java | 53 +++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/AbstractMicrosoftStsTokenResponseHandler.java b/common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/AbstractMicrosoftStsTokenResponseHandler.java index 39249e9d48..01af2ebead 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/AbstractMicrosoftStsTokenResponseHandler.java +++ b/common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/AbstractMicrosoftStsTokenResponseHandler.java @@ -31,6 +31,7 @@ import com.microsoft.identity.common.java.exception.ClientException; import com.microsoft.identity.common.java.flighting.CommonFlight; import com.microsoft.identity.common.java.flighting.CommonFlightsManager; +import com.microsoft.identity.common.java.logging.Logger; import com.microsoft.identity.common.java.net.HttpResponse; import com.microsoft.identity.common.java.opentelemetry.AttributeName; import com.microsoft.identity.common.java.opentelemetry.SpanExtension; @@ -43,6 +44,7 @@ import com.microsoft.identity.common.java.util.HeaderSerializationUtil; import com.microsoft.identity.common.java.util.ObjectMapper; import com.microsoft.identity.common.java.util.ResultUtil; +import com.microsoft.identity.common.java.util.StringUtil; import java.net.HttpURLConnection; import java.util.HashMap; @@ -109,11 +111,25 @@ public TokenResult handleTokenResponse(@NonNull final HttpResponse response) thr } final String clientDataHeader = response.getHeaderValue(X_MS_CLIENTDATA, 0); - if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) { - final ClientDataInfo clientDataInfo = ClientDataInfo.fromPipeDelimited(clientDataHeader); - if (null != clientDataInfo) { - clientDataInfo.emitToSpan(); - result.setClientDataInfo(clientDataInfo); + if (!StringUtil.isNullOrEmpty(clientDataHeader) + && CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) { + // eSTS URL-encodes the pipe-delimited value in the response header (e.g. "m%7C0x800482A5%7C%7C..."). + // ClientDataInfo.fromPipeDelimited expects an already-decoded value (its contract matches the + // authorize-endpoint path, where UrlUtil#getParameters has already decoded the query param). + // Decode here so the token-endpoint header path produces the same shape. + String decodedClientDataHeader = null; + try { + decodedClientDataHeader = StringUtil.urlFormDecode(clientDataHeader); + } catch (final Exception e) { + // Malformed percent-encoding shouldn't break token parsing; swallow and fall through. + Logger.warn(methodTag, "Failed to URL-decode x-ms-clientdata header: " + e.getMessage()); + } + if (decodedClientDataHeader != null) { + final ClientDataInfo clientDataInfo = ClientDataInfo.fromPipeDelimited(decodedClientDataHeader); + if (null != clientDataInfo) { + clientDataInfo.emitToSpan(); + result.setClientDataInfo(clientDataInfo); + } } } diff --git a/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsTokenResponseHandlerTest.java b/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsTokenResponseHandlerTest.java index 0f440f0161..bc9c448cae 100644 --- a/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsTokenResponseHandlerTest.java +++ b/common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsTokenResponseHandlerTest.java @@ -139,6 +139,59 @@ public void testHandleTokenResponse_withClientDataHeader_attributesEmitted() { } } + @SneakyThrows + @Test + public void testHandleTokenResponse_withUrlEncodedClientDataHeader_attributesEmitted() { + // eSTS URL-encodes pipe separators in the response header (e.g. "%7C" for "|"). + // Real-world example: x-ms-clientdata=[m%7C0x800482A5%7C%7Cmicrosoftonline.com%7Cnone] + final String encodedClientDataHeader = "m%7C0x800482A5%7C%7Cmicrosoftonline.com%7Cnone"; + + final HashMap> headers = new HashMap<>(); + headers.put("Content-Type", Collections.singletonList("application/json; charset=utf-8")); + headers.put(HttpConstants.HeaderField.X_MS_CLIENTDATA, + Collections.singletonList(encodedClientDataHeader)); + + final HttpResponse response = new HttpResponse(200, MOCK_TOKEN_SUCCESS_RESPONSE, headers); + final MicrosoftStsTokenResponseHandler handler = new MicrosoftStsTokenResponseHandler(); + + final Span mockSpan = mock(Span.class); + when(mockSpan.setAttribute(Mockito.anyString(), Mockito.anyString())).thenReturn(mockSpan); + + try (MockedStatic mockedExtension = Mockito.mockStatic(SpanExtension.class)) { + mockedExtension.when(SpanExtension::current).thenReturn(mockSpan); + + final TokenResult tokenResult = handler.handleTokenResponse(response); + + Assert.assertNotNull(tokenResult); + Assert.assertTrue(tokenResult.getSuccess()); + Assert.assertNotNull(tokenResult.getClientDataInfo()); + verify(mockSpan).setAttribute(AttributeName.server_error.name(), "0x800482A5"); + verify(mockSpan).setAttribute(AttributeName.account_type.name(), "MSA"); + } + } + + @SneakyThrows + @Test + public void testHandleTokenResponse_withMalformedPercentEncoding_doesNotCrash() { + // Lone '%' is invalid percent-encoding; decode should fail gracefully and skip ClientDataInfo. + final String malformedHeader = "e|AADSTS50058|%ZZ|us|public"; + + final HashMap> headers = new HashMap<>(); + headers.put("Content-Type", Collections.singletonList("application/json; charset=utf-8")); + headers.put(HttpConstants.HeaderField.X_MS_CLIENTDATA, + Collections.singletonList(malformedHeader)); + + final HttpResponse response = new HttpResponse(200, MOCK_TOKEN_SUCCESS_RESPONSE, headers); + final MicrosoftStsTokenResponseHandler handler = new MicrosoftStsTokenResponseHandler(); + + final TokenResult tokenResult = handler.handleTokenResponse(response); + + Assert.assertNotNull(tokenResult); + Assert.assertTrue(tokenResult.getSuccess()); + // Malformed encoding => null ClientDataInfo, but token result still valid. + Assert.assertNull(tokenResult.getClientDataInfo()); + } + @SneakyThrows @Test public void testHandleTokenResponse_noClientDataHeader_doesNotCrash() { From f005147a7d82ec5b3de1e5c673cd7fd965b431a3 Mon Sep 17 00:00:00 2001 From: fadidurah Date: Wed, 27 May 2026 15:56:29 -0400 Subject: [PATCH 6/6] Revert "Build: publish libraries directly to NewAndroid feed" This reverts commit d98d3e1f924f79d7980d520309a220b99b6186b4. --- LabApiUtilities/build.gradle | 2 +- common/build.gradle | 2 +- common4j/build.gradle | 2 +- keyvault/build.gradle | 2 +- labapi/build.gradle | 2 +- testutils/build.gradle | 2 +- uiautomationutilities/build.gradle | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LabApiUtilities/build.gradle b/LabApiUtilities/build.gradle index 790d6b0601..b991327e96 100644 --- a/LabApiUtilities/build.gradle +++ b/LabApiUtilities/build.gradle @@ -114,7 +114,7 @@ publishing { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/common/build.gradle b/common/build.gradle index 43e5370878..95ce168bac 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -420,7 +420,7 @@ afterEvaluate { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" credentials { username project.vstsUsername password project.vstsMavenAccessToken diff --git a/common4j/build.gradle b/common4j/build.gradle index 716ae1c664..fa56a27ca3 100644 --- a/common4j/build.gradle +++ b/common4j/build.gradle @@ -197,7 +197,7 @@ publishing { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/keyvault/build.gradle b/keyvault/build.gradle index 32ff9cab88..ace7611de8 100644 --- a/keyvault/build.gradle +++ b/keyvault/build.gradle @@ -29,7 +29,7 @@ publishing { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/labapi/build.gradle b/labapi/build.gradle index 58d2d923cc..c7be9b067b 100644 --- a/labapi/build.gradle +++ b/labapi/build.gradle @@ -30,7 +30,7 @@ publishing { repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/testutils/build.gradle b/testutils/build.gradle index 89c8a55e8e..c811c23cf5 100644 --- a/testutils/build.gradle +++ b/testutils/build.gradle @@ -136,7 +136,7 @@ project.afterEvaluate{ repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken diff --git a/uiautomationutilities/build.gradle b/uiautomationutilities/build.gradle index 5266b05c11..29364bc0f5 100644 --- a/uiautomationutilities/build.gradle +++ b/uiautomationutilities/build.gradle @@ -182,7 +182,7 @@ project.afterEvaluate{ repositories { maven { name "vsts-maven-adal-android" - url "https://identitydivision.pkgs.visualstudio.com/_packaging/NewAndroid/maven/v1" + url "https://identitydivision.pkgs.visualstudio.com/_packaging/AndroidADAL/maven/v1" credentials { username project.ext.vstsUsername password project.ext.vstsMavenAccessToken