Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion rt/rs/security/sso/oidc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
</cxf.osgi.import>
<!-- this configures the surefire plugin to run your tests with the javaagent enabled -->
<!-- (openJPA loadtime weaving) -->
<cxf.surefire.fork.vmargs.extra>-javaagent:${org.apache.openjpa:openjpa:jar}</cxf.surefire.fork.vmargs.extra>
<cxf.surefire.fork.vmargs.extra>-javaagent:${org.apache.openjpa:openjpa:jar} -javaagent:${org.mockito:mockito-core:jar}</cxf.surefire.fork.vmargs.extra>
</properties>
<dependencies>
<dependency>
Expand All @@ -64,6 +64,12 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${cxf.mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,33 @@ private MultivaluedMap<String, String> toRequestState(ContainerRequestContext rc
rc.setEntityStream(new ByteArrayInputStream(StringUtils.toBytesUTF8(body)));

}
// The "state" carried here is read back by the sign-in completion service and returned
// as a redirect Location, so a caller-supplied value collides with the redirect query
// the filter itself writes. Anything that is not within this application's own origin is
// dropped, otherwise completion would become an open redirect.
String location = requestState.getFirst("state");
if (location != null && !isSameOrigin(rc, location)) {
requestState.remove("state");
}
return requestState;
}
private boolean isSameOrigin(ContainerRequestContext rc, String location) {
final URI uri;
try {
uri = URI.create(location);
} catch (IllegalArgumentException ex) {
return false;
}
if (uri.getScheme() == null && uri.getAuthority() == null) {
// a path-only reference is resolved by the browser against the current request
return true;
}
URI base = rc.getUriInfo().getAbsolutePath();
return uri.getScheme() != null
&& uri.getScheme().equalsIgnoreCase(base.getScheme())
&& uri.getAuthority() != null
&& uri.getAuthority().equalsIgnoreCase(base.getAuthority());
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* 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.oidc.rp;

import java.lang.reflect.Method;
import java.net.URI;

import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.UriInfo;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class OidcRpAuthenticationFilterTest {

private static final URI ABSOLUTE_PATH = URI.create("https://app.example.com:8080/services/rp/complete");

@Test
public void testDropsCrossOriginState() {
MultivaluedMap<String, String> state = requestState("https://evil.example.com/phish");
assertFalse(state.containsKey("state"));
}

@Test
Comment thread
reta marked this conversation as resolved.
public void testDropsProtocolRelativeState() {
MultivaluedMap<String, String> state = requestState("//evil.example.com/phish");
assertFalse(state.containsKey("state"));
}

@Test
public void testDropsUserinfoHostConfusionState() {
MultivaluedMap<String, String> state = requestState("https://app.example.com:8080@evil.example.com/phish");
assertFalse(state.containsKey("state"));
}

@Test
public void testKeepsSameOriginState() {
MultivaluedMap<String, String> state = requestState("https://app.example.com:8080/services/protected");
assertTrue(state.containsKey("state"));
assertEquals("https://app.example.com:8080/services/protected", state.getFirst("state"));
}

@Test
public void testKeepsRelativeState() {
MultivaluedMap<String, String> state = requestState("/services/protected");
assertTrue(state.containsKey("state"));
assertEquals("/services/protected", state.getFirst("state"));
}

private MultivaluedMap<String, String> requestState(String stateLocation) {
MultivaluedMap<String, String> query = new MultivaluedHashMap<>();
query.putSingle("state", stateLocation);

UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getQueryParameters(true)).thenReturn(query);
when(uriInfo.getAbsolutePath()).thenReturn(ABSOLUTE_PATH);

ContainerRequestContext rc = mock(ContainerRequestContext.class);
when(rc.getUriInfo()).thenReturn(uriInfo);
when(rc.getMediaType()).thenReturn(null);

return invokeToRequestState(new OidcRpAuthenticationFilter(), rc);
}

@SuppressWarnings("unchecked")
private static MultivaluedMap<String, String> invokeToRequestState(OidcRpAuthenticationFilter filter,
ContainerRequestContext rc) {
try {
Method method = OidcRpAuthenticationFilter.class.getDeclaredMethod("toRequestState",
ContainerRequestContext.class);
method.setAccessible(true);
return (MultivaluedMap<String, String>)method.invoke(filter, rc);
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
}
Loading