diff --git a/components/identity-core/org.wso2.carbon.identity.core/pom.xml b/components/identity-core/org.wso2.carbon.identity.core/pom.xml index 43ce964c798d..930814a589e9 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/pom.xml +++ b/components/identity-core/org.wso2.carbon.identity.core/pom.xml @@ -165,6 +165,18 @@ h2 test + + org.apache.tomcat + tomcat-coyote + ${apache.tomcat-catalina.version} + test + + + org.apache.tomcat + tomcat-util + ${apache.tomcat-catalina.version} + test + diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java index 99c40a8f8eb8..1178a6990145 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java @@ -28,8 +28,10 @@ import org.wso2.carbon.identity.core.internal.context.OrganizationResolver; import java.io.IOException; +import java.util.Enumeration; import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; public class IdentityContextCreatorValve extends ValveBase { @@ -48,6 +50,23 @@ public IdentityContextCreatorValve() { public void invoke(Request request, Response response) throws IOException, ServletException { try { + // Enforce RFC 9110 ยง11.6.1: The Authorization header is a single-value field. + // Rejecting requests with multiple Authorization headers early at the transport/valve layer + // prevents potential header manipulation and security vulnerabilities. + Enumeration authHeaders = request.getHeaders("Authorization"); + if (authHeaders != null) { + int authHeaderCount = 0; + while (authHeaders.hasMoreElements()) { + authHeaders.nextElement(); + authHeaderCount++; + if (authHeaderCount > 1) { + LOG.warn("Multiple Authorization headers detected."); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Multiple Authorization headers are not allowed."); + return; + } + } + } + initIdentityContext(); initRequest(request); initAccessTokenIssuedOrganization(request.getRequestURI()); diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java new file mode 100644 index 000000000000..bafdb19e7644 --- /dev/null +++ b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java @@ -0,0 +1,83 @@ +package org.wso2.carbon.identity.core.context.valve; + +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.servlet.http.HttpServletResponse; +import java.util.Enumeration; +import java.util.Vector; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class IdentityContextCreatorValveTest { + + private IdentityContextCreatorValve identityContextCreatorValve; + + @Mock + private Valve nextValve; + + private AutoCloseable closeable; + + @BeforeMethod + public void setUp() { + System.setProperty("carbon.home", "."); + closeable = MockitoAnnotations.openMocks(this); + identityContextCreatorValve = new IdentityContextCreatorValve(); + identityContextCreatorValve.setNext(nextValve); + } + + @AfterMethod + public void tearDown() throws Exception { + if (closeable != null) { + closeable.close(); + } + } + + @Test + public void testInvokeWithMultipleAuthorizationHeaders() throws Exception { + Vector headers = new Vector<>(); + headers.add("Bearer token1"); + headers.add("Bearer token2"); + final Enumeration headerEnum = headers.elements(); + + Request request = new Request(null) { + @Override + public Enumeration getHeaders(String name) { + if ("Authorization".equals(name)) { + return headerEnum; + } + return super.getHeaders(name); + } + + @Override + public String getRequestURI() { + return "/oauth2/userinfo"; + } + }; + + final int[] errorStatus = new int[1]; + final String[] errorMessage = new String[1]; + Response response = new Response() { + @Override + public void sendError(int status, String message) throws java.io.IOException { + errorStatus[0] = status; + errorMessage[0] = message; + } + }; + + identityContextCreatorValve.invoke(request, response); + + org.testng.Assert.assertEquals(errorStatus[0], HttpServletResponse.SC_BAD_REQUEST); + org.testng.Assert.assertEquals(errorMessage[0], "Multiple Authorization headers are not allowed."); + verify(nextValve, never()).invoke(any(Request.class), any(Response.class)); + } +}