This is an automated email from the ASF dual-hosted git repository.

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 04a1f0d29f1d CAMEL-26162: camel-workday - the token request sends the 
Base64 Basic header, and the access token is read as JSON (#28691)
04a1f0d29f1d is described below

commit 04a1f0d29f1d797ae00cb626f4d0dd92c81ca243
Author: Vignesh Nayak Manel <[email protected]>
AuthorDate: Thu Oct 29 19:45:53 2026 +0530

    CAMEL-26162: camel-workday - the token request sends the Base64 Basic 
header, and the access token is read as JSON (#28691)
    
    The Authorization header of the token request was "Basic " + 
Arrays.toString of
    the Base64 bytes ("Basic [89, 50, ...]"), so Workday rejected it and no 
access
    token could be obtained (since the HttpComponents 5 upgrade, CAMEL-18995). 
The
    access token was cut from the response at fixed offsets, which only worked 
for
    one exact JSON layout. It is now read as the access_token field of the JSON
    response with camel-util-json.
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 components/camel-workday/pom.xml                   |   4 +
 .../workday/auth/AuthClientForIntegration.java     |  25 ++---
 .../workday/auth/AuthClientForIntegrationTest.java | 124 +++++++++++++++++++++
 3 files changed, 139 insertions(+), 14 deletions(-)

diff --git a/components/camel-workday/pom.xml b/components/camel-workday/pom.xml
index 4687d5adaf04..aeaba270d65b 100644
--- a/components/camel-workday/pom.xml
+++ b/components/camel-workday/pom.xml
@@ -43,6 +43,10 @@
       <groupId>org.apache.camel</groupId>
       <artifactId>camel-support</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-util-json</artifactId>
+    </dependency>
 
     <!-- testing -->
     <dependency>
diff --git 
a/components/camel-workday/src/main/java/org/apache/camel/component/workday/auth/AuthClientForIntegration.java
 
b/components/camel-workday/src/main/java/org/apache/camel/component/workday/auth/AuthClientForIntegration.java
index d24bca4c3bac..712ac2ecdc16 100644
--- 
a/components/camel-workday/src/main/java/org/apache/camel/component/workday/auth/AuthClientForIntegration.java
+++ 
b/components/camel-workday/src/main/java/org/apache/camel/component/workday/auth/AuthClientForIntegration.java
@@ -20,11 +20,12 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Base64;
 import java.util.List;
 
 import org.apache.camel.component.workday.WorkdayConfiguration;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
 import org.apache.hc.client5.http.classic.methods.HttpPost;
 import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
 import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
@@ -80,7 +81,7 @@ public class AuthClientForIntegration implements 
AuthenticationClient {
 
                     try (ByteArrayOutputStream baos = new 
ByteArrayOutputStream()) {
                         httpResponse.getEntity().writeTo(baos);
-                        return parseResponse(baos.toString());
+                        return 
parseResponse(baos.toString(StandardCharsets.UTF_8));
                     }
                 });
 
@@ -95,25 +96,21 @@ public class AuthClientForIntegration implements 
AuthenticationClient {
         HttpPost postMethod = new HttpPost(tokenUrl);
         postMethod.addHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE);
         postMethod.addHeader(AUTHORIZATION_HEADER,
-                "Basic " + Arrays.toString(Base64.getEncoder()
-                        .encode((workdayConfiguration.getClientId() + ":" + 
workdayConfiguration.getClientSecret())
-                                .getBytes())));
+                "Basic " + Base64.getEncoder()
+                        .encodeToString((workdayConfiguration.getClientId() + 
":" + workdayConfiguration.getClientSecret())
+                                .getBytes(StandardCharsets.UTF_8)));
         postMethod.setEntity(new UrlEncodedFormEntity(nvps, 
StandardCharsets.UTF_8));
 
         return postMethod;
     }
 
     private String parseResponse(String response) {
-
-        int tokenIdx = response.indexOf(ACCESS_TOKEN);
-
-        if (tokenIdx < 1) {
-            throw new IllegalStateException("No valid access token response.");
+        JsonObject json = Jsoner.deserialize(response, (JsonObject) null);
+        Object accessToken = json != null ? json.get(ACCESS_TOKEN) : null;
+        if (accessToken instanceof String token && !token.isEmpty()) {
+            return token;
         }
-
-        response = response.substring(response.indexOf(ACCESS_TOKEN) + 16, 
response.length() - 3);
-
-        return response;
+        throw new IllegalStateException("No valid access token response.");
     }
 
 }
diff --git 
a/components/camel-workday/src/test/java/org/apache/camel/component/workday/auth/AuthClientForIntegrationTest.java
 
b/components/camel-workday/src/test/java/org/apache/camel/component/workday/auth/AuthClientForIntegrationTest.java
new file mode 100644
index 000000000000..9a94fafab070
--- /dev/null
+++ 
b/components/camel-workday/src/test/java/org/apache/camel/component/workday/auth/AuthClientForIntegrationTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.workday.auth;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.component.workday.WorkdayConfiguration;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.io.CloseMode;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class AuthClientForIntegrationTest {
+
+    @Test
+    void tokenRequestHasTheBasicAuthorizationHeader() throws Exception {
+        TokenEndpoint tokenEndpoint = new 
TokenEndpoint("{\"access_token\":\"the-token\"}");
+
+        assertEquals("the-token", new 
AuthClientForIntegration(configuration()).getBearerToken(tokenEndpoint));
+
+        ClassicHttpRequest request = tokenEndpoint.request;
+        assertEquals("https://impl.workday.com/ccx/oauth2/camel/token";, 
request.getUri().toString());
+        // Base64 of client-id:client-secret
+        assertEquals("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ=", 
request.getFirstHeader("Authorization").getValue());
+        
assertEquals("grant_type=refresh_token&refresh_token=the-refresh-token",
+                EntityUtils.toString(request.getEntity(), 
StandardCharsets.UTF_8));
+    }
+
+    @Test
+    void accessTokenIsReadFromAnyJsonLayout() throws Exception {
+        TokenEndpoint tokenEndpoint = new TokenEndpoint(
+                "{\n  \"access_token\" : \"the-token\",\n  \"token_type\" : 
\"Bearer\",\n  \"expires_in\" : 3600\n}");
+
+        assertEquals("the-token", new 
AuthClientForIntegration(configuration()).getBearerToken(tokenEndpoint));
+    }
+
+    @Test
+    void responseWithoutAccessTokenIsRejected() {
+        AuthClientForIntegration client = new 
AuthClientForIntegration(configuration());
+
+        for (String response : new String[] {
+                "{\"error\":\"invalid_client\",\"error_description\":\"no 
access_token for you\"}", "access_token", "" }) {
+            IllegalStateException e = assertThrows(IllegalStateException.class,
+                    () -> client.getBearerToken(new TokenEndpoint(response)), 
response);
+            assertEquals("No valid access token response.", e.getMessage());
+        }
+    }
+
+    private static WorkdayConfiguration configuration() {
+        WorkdayConfiguration configuration = new WorkdayConfiguration();
+        configuration.setHost("impl.workday.com");
+        configuration.setTenant("camel");
+        configuration.setClientId("client-id");
+        configuration.setClientSecret("client-secret");
+        configuration.setTokenRefresh("the-refresh-token");
+        return configuration;
+    }
+
+    /**
+     * Records the token request and answers it with the given JSON, in place 
of the Workday token endpoint.
+     */
+    private static final class TokenEndpoint extends CloseableHttpClient {
+
+        private final String response;
+        private ClassicHttpRequest request;
+
+        private TokenEndpoint(String response) {
+            this.response = response;
+        }
+
+        @Override
+        public <T> T execute(ClassicHttpRequest request, 
HttpClientResponseHandler<? extends T> responseHandler)
+                throws IOException {
+            this.request = request;
+            try (BasicClassicHttpResponse httpResponse = new 
BasicClassicHttpResponse(HttpStatus.SC_OK)) {
+                httpResponse.setEntity(new StringEntity(response, 
ContentType.APPLICATION_JSON));
+                return responseHandler.handleResponse(httpResponse);
+            } catch (HttpException e) {
+                throw new IOException(e);
+            }
+        }
+
+        @Override
+        protected CloseableHttpResponse doExecute(HttpHost target, 
ClassicHttpRequest request, HttpContext context) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void close() {
+        }
+
+        @Override
+        public void close(CloseMode closeMode) {
+        }
+    }
+}

Reply via email to