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

coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/4.1.x-fixes by this push:
     new 617b22ded39 Removing control characters from OAuth2 realm and logs 
(#3136)
617b22ded39 is described below

commit 617b22ded390a2e18a5803ce11b539dc59677d6d
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri May 22 15:00:52 2026 +0100

    Removing control characters from OAuth2 realm and logs (#3136)
    
    (cherry picked from commit 9fc78f6302a7d16bed9ab5893c55a23d9d54701e)
---
 .../oauth2/services/AbstractTokenService.java      |   5 +-
 .../security/oauth2/utils/AuthorizationUtils.java  |   6 +-
 .../oauth2/services/AbstractTokenServiceTest.java  | 130 +++++++++++++++++++++
 .../oauth2/utils/AuthorizationUtilsTest.java       |  88 ++++++++++++++
 .../oauth2/src/test/resources/logging.properties   |  74 ++++++++++++
 5 files changed, 300 insertions(+), 3 deletions(-)

diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractTokenService.java
 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractTokenService.java
index 683df9b04d3..0f42687bb85 100644
--- 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractTokenService.java
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractTokenService.java
@@ -254,17 +254,18 @@ public class AbstractTokenService extends 
AbstractOAuthService {
             return null;
         }
         Client client = null;
+        String sanitizedClientId = 
AuthorizationUtils.stripControlCharacters(clientId);
         try {
             client = getValidClient(clientId, clientSecret, params);
         } catch (OAuthServiceException ex) {
-            LOG.warning("No valid client found for clientId: " + clientId);
+            LOG.warning("No valid client found for clientId: " + 
sanitizedClientId);
             if (ex.getError() != null) {
                 reportInvalidClient(ex.getError());
                 return null;
             }
         }
         if (client == null) {
-            LOG.warning("No valid client found for clientId: " + clientId);
+            LOG.warning("No valid client found for clientId: " + 
sanitizedClientId);
             reportInvalidClient();
         }
         return client;
diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/AuthorizationUtils.java
 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/AuthorizationUtils.java
index 24d5bcf3160..7e0c7e0a384 100644
--- 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/AuthorizationUtils.java
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/AuthorizationUtils.java
@@ -106,7 +106,7 @@ public final class AuthorizationUtils {
         }
         if (sb.length() > 0) {
             if (realm != null) {
-                sb.append(" realm=\"").append(realm).append('"');
+                sb.append(" 
realm=\"").append(stripControlCharacters(realm)).append('"');
             }
             rb.header(HttpHeaders.WWW_AUTHENTICATE, sb.toString());
         }
@@ -115,4 +115,8 @@ public final class AuthorizationUtils {
         }
         throw ExceptionUtils.toNotAuthorizedException(cause, rb.build());
     }
+
+    public static String stripControlCharacters(String value) {
+        return value.replaceAll("[\\p{Cntrl}&&[^\\t]]", "_");
+    }
 }
diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/AbstractTokenServiceTest.java
 
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/AbstractTokenServiceTest.java
new file mode 100644
index 00000000000..4e56b366987
--- /dev/null
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/AbstractTokenServiceTest.java
@@ -0,0 +1,130 @@
+/**
+ * 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.cxf.rs.security.oauth2.services;
+
+import jakarta.ws.rs.core.MultivaluedMap;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.rs.security.oauth2.common.Client;
+import org.apache.cxf.rs.security.oauth2.common.OAuthError;
+import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
+import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+
+public class AbstractTokenServiceTest {
+
+    @Test
+    public void testGetClientReportsInvalidClientWhenClientNotFound() {
+        TestTokenService service = new TestTokenService();
+
+        InvalidClientException ex = assertThrows(InvalidClientException.class,
+            () -> service.callGetClient("missing-client", "secret", new 
MetadataMap<>()));
+
+        assertNotNull(ex.getError());
+        assertEquals(OAuthConstants.INVALID_CLIENT, ex.getError().getError());
+    }
+
+    @Test
+    public void testInvalidClientInvalidCharactersHandled() {
+        TestTokenService service = new TestTokenService();
+
+        String clientId = "alice\r\n\r\n\r\n[FAKE]+Admin+login+successful";
+        InvalidClientException ex = assertThrows(InvalidClientException.class,
+            () -> service.callGetClient(clientId, "secret", new 
MetadataMap<>()));
+
+        assertNotNull(ex.getError());
+        assertEquals(OAuthConstants.INVALID_CLIENT, ex.getError().getError());
+    }
+
+    @Test
+    public void testGetClientReportsCustomErrorWhenProviderThrowsIt() {
+        TestTokenService service = new TestTokenService();
+        OAuthError customError = new 
OAuthError(OAuthConstants.UNAUTHORIZED_CLIENT);
+        service.setException(new OAuthServiceException(customError));
+
+        InvalidClientException ex = assertThrows(InvalidClientException.class,
+            () -> service.callGetClient("client", "secret", new 
MetadataMap<>()));
+
+        assertSame(customError, ex.getError());
+    }
+
+    @Test
+    public void testGetClientReturnsClientWhenFound() {
+        TestTokenService service = new TestTokenService();
+        Client expected = new Client("client", "secret", true);
+        service.setClient(expected);
+
+        Client actual = service.callGetClient("client", "secret", new 
MetadataMap<>());
+        assertSame(expected, actual);
+    }
+
+    private static final class TestTokenService extends AbstractTokenService {
+        private Client client;
+        private OAuthServiceException exception;
+
+        Client callGetClient(String clientId, String clientSecret, 
MultivaluedMap<String, String> params) {
+            return getClient(clientId, clientSecret, params);
+        }
+
+        void setClient(Client client) {
+            this.client = client;
+        }
+
+        void setException(OAuthServiceException exception) {
+            this.exception = exception;
+        }
+
+        @Override
+        protected Client getValidClient(String clientId, String clientSecret, 
MultivaluedMap<String, String> params)
+            throws OAuthServiceException {
+            if (exception != null) {
+                throw exception;
+            }
+            return client;
+        }
+
+        @Override
+        protected void reportInvalidClient() {
+            throw new InvalidClientException(new 
OAuthError(OAuthConstants.INVALID_CLIENT));
+        }
+
+        @Override
+        protected void reportInvalidClient(OAuthError error) {
+            throw new InvalidClientException(error);
+        }
+    }
+
+    private static class InvalidClientException extends RuntimeException {
+        private static final long serialVersionUID = 1L;
+        private final OAuthError error;
+
+        InvalidClientException(OAuthError error) {
+            this.error = error;
+        }
+
+        OAuthError getError() {
+            return error;
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/utils/AuthorizationUtilsTest.java
 
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/utils/AuthorizationUtilsTest.java
index f2e2864fd38..9f0cc0be46d 100644
--- 
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/utils/AuthorizationUtilsTest.java
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/utils/AuthorizationUtilsTest.java
@@ -19,6 +19,7 @@
 package org.apache.cxf.rs.security.oauth2.utils;
 
 import java.util.Arrays;
+import java.util.Base64;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -100,6 +101,35 @@ public class AuthorizationUtilsTest {
         }
     }
 
+    @Test
+    public void testThrowAuthorizationFailureWithRealm() {
+        try {
+            
AuthorizationUtils.throwAuthorizationFailure(Collections.singleton("Basic"), 
"oauth");
+            fail("WebApplicationException expected");
+        } catch (WebApplicationException ex) {
+            Response r = ex.getResponse();
+            assertEquals(401, r.getStatus());
+            String value = r.getHeaderString(HttpHeaders.WWW_AUTHENTICATE);
+            assertNotNull(value);
+            assertEquals("Basic realm=\"oauth\"", value);
+        }
+    }
+
+    @Test
+    public void testThrowAuthorizationFailureWithRealmInvalidCharacters() {
+        String realm = "oauth\r\n\r\n\r\nX-Injected: true";
+        try {
+            
AuthorizationUtils.throwAuthorizationFailure(Collections.singleton("Basic"), 
realm);
+            fail("WebApplicationException expected");
+        } catch (WebApplicationException ex) {
+            Response r = ex.getResponse();
+            assertEquals(401, r.getStatus());
+            String value = r.getHeaderString(HttpHeaders.WWW_AUTHENTICATE);
+            assertNotNull(value);
+            assertEquals("Basic realm=" + "\"" + 
AuthorizationUtils.stripControlCharacters(realm) + "\"", value);
+        }
+    }
+
     @Test
     public void getAuthorizationParts() {
         String type = "type";
@@ -123,4 +153,62 @@ public class AuthorizationUtilsTest {
                 AuthorizationUtils.getAuthorizationParts(mc, new 
HashSet<>(Arrays.asList("another", type))));
     }
 
+    @Test
+    public void testGetAuthorizationPartsWithMultipleHeadersFails() {
+        Message m = new MessageImpl();
+        m.put(Message.PROTOCOL_HEADERS, 
Collections.singletonMap(HttpHeaders.AUTHORIZATION,
+                Arrays.asList("Basic YWxpY2U6c2VjcmV0", "Bearer token")));
+        MessageContext mc = new MessageContextImpl(m);
+
+        try {
+            AuthorizationUtils.getAuthorizationParts(mc);
+            fail("WebApplicationException expected");
+        } catch (WebApplicationException ex) {
+            assertEquals(401, ex.getResponse().getStatus());
+        }
+    }
+
+    @Test
+    public void testGetBasicAuthParts() {
+        String basicAuthData = 
Base64.getEncoder().encodeToString("alice:secret".getBytes());
+
+        String[] parts = AuthorizationUtils.getBasicAuthParts(basicAuthData);
+        assertArrayEquals(new String[] {"alice", "secret"}, parts);
+    }
+
+    @Test
+    public void testGetBasicAuthPartsInvalidBase64() {
+        try {
+            AuthorizationUtils.getBasicAuthParts("not-base64");
+            fail("WebApplicationException expected");
+        } catch (WebApplicationException ex) {
+            assertEquals(401, ex.getResponse().getStatus());
+        }
+    }
+
+    @Test
+    public void testGetBasicAuthPartsMissingSeparator() {
+        String basicAuthData = 
Base64.getEncoder().encodeToString("alice".getBytes());
+
+        try {
+            AuthorizationUtils.getBasicAuthParts(basicAuthData);
+            fail("WebApplicationException expected");
+        } catch (WebApplicationException ex) {
+            assertEquals(401, ex.getResponse().getStatus());
+        }
+    }
+
+    @Test
+    public void testGetBasicAuthUserInfo() {
+        String basicAuthData = 
Base64.getEncoder().encodeToString("alice:secret".getBytes());
+
+        Message m = new MessageImpl();
+        m.put(Message.PROTOCOL_HEADERS, 
Collections.singletonMap(HttpHeaders.AUTHORIZATION,
+                Collections.singletonList("Basic " + basicAuthData)));
+        MessageContext mc = new MessageContextImpl(m);
+
+        String[] userInfo = AuthorizationUtils.getBasicAuthUserInfo(mc);
+        assertArrayEquals(new String[] {"alice", "secret"}, userInfo);
+    }
+
 }
diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/test/resources/logging.properties 
b/rt/rs/security/oauth-parent/oauth2/src/test/resources/logging.properties
new file mode 100644
index 00000000000..4889366a3ab
--- /dev/null
+++ b/rt/rs/security/oauth-parent/oauth2/src/test/resources/logging.properties
@@ -0,0 +1,74 @@
+#
+#
+#    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.
+#
+#
+############################################################
+# Default Logging Configuration File
+#
+# You can use a different file by specifying a filename
+# with the java.util.logging.config.file system property.  
+# For example java -Djava.util.logging.config.file=myfile
+############################################################
+
+############################################################
+# Global properties
+############################################################
+
+# "handlers" specifies a comma separated list of log Handler 
+# classes.  These handlers will be installed during VM startup.
+# Note that these classes must be on the system classpath.
+# By default we only configure a ConsoleHandler, which will only
+# show messages at the INFO and above levels.
+#handlers= java.util.logging.ConsoleHandler
+
+# To also add the FileHandler, use the following line instead.
+#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
+
+# Default global logging level.
+# This specifies which kinds of events are logged across
+# all loggers.  For any given facility this global level
+# can be overriden by a facility specific level
+# Note that the ConsoleHandler also has a separate level
+# setting to limit messages printed to the console.
+.level= INFO
+
+############################################################
+# Handler specific properties.
+# Describes specific configuration info for Handlers.
+############################################################
+
+# default file output is in user's home directory.
+java.util.logging.FileHandler.pattern = %h/java%u.log
+java.util.logging.FileHandler.limit = 50000
+java.util.logging.FileHandler.count = 1
+java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
+
+# Limit the message that are printed on the console to INFO and above.
+java.util.logging.ConsoleHandler.level = INFO
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+
+
+############################################################
+# Facility specific properties.
+# Provides extra control for each logger.
+############################################################
+
+# For example, set the com.xyz.foo logger to only log SEVERE
+# messages:
+#com.xyz.foo.level = SEVERE

Reply via email to