Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -57,18 +57,42 @@ public Response completeAuthentication(@Context OidcClientTokenContext oidcConte
URI redirectUri = null;
MultivaluedMap<String, String> state = oidcContext.getState();
String location = state != null ? state.getFirst("state") : null;
if (location == null && defaultLocation != null) {
if (location != null) {
Comment thread
reta marked this conversation as resolved.
Outdated
URI requestedUri = URI.create(UrlUtils.urlDecode(location));
if (isSameOrigin(requestedUri)) {
redirectUri = requestedUri;
}
}
if (redirectUri == null && defaultLocation != null) {
String basePath = (String)mc.get("http.base.path");
redirectUri = UriBuilder.fromUri(basePath).path(defaultLocation).build();
} else if (location != null) {
redirectUri = URI.create(UrlUtils.urlDecode(location));
}
if (redirectUri != null) {
return Response.seeOther(redirectUri).build();
}
return Response.ok(oidcContext).build();
}

// The location is taken from the request state, so it can only be trusted as long as it
// stays within this application's own origin. An absolute value pointing at a different
// host (or a protocol-relative "//host" reference) would turn sign-in completion into an
// open redirect, so anything that is not same-origin is ignored here.
private boolean isSameOrigin(URI location) {
if (location.getScheme() == null && location.getAuthority() == null) {
// a path-only reference is resolved by the browser against the current request
return true;
}
String basePath = (String)mc.get("http.base.path");
if (basePath == null) {
return false;
}
URI base = URI.create(basePath);
return location.getScheme() != null
&& location.getScheme().equalsIgnoreCase(base.getScheme())
&& location.getAuthority() != null
&& location.getAuthority().equalsIgnoreCase(base.getAuthority());
}

public void setDefaultLocation(String defaultLocation) {
this.defaultLocation = defaultLocation;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* 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.Field;
import java.net.URI;

import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;

import org.apache.cxf.jaxrs.ext.MessageContext;
import org.apache.cxf.rs.security.oauth2.client.ClientTokenContextManager;

import org.junit.Test;

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

public class OidcRpAuthenticationServiceTest {

private static final String BASE_PATH = "https://app.example.com:8080/services/";

@Test
public void testRejectsCrossOriginRedirectFromState() {
Response response = complete("https://evil.example.com/phish");
assertEquals(200, response.getStatus());
assertNull(response.getLocation());
}

@Test
public void testRejectsProtocolRelativeRedirectFromState() {
Response response = complete("//evil.example.com/phish");
assertEquals(200, response.getStatus());
assertNull(response.getLocation());
}

@Test
public void testRejectsUserinfoHostConfusionFromState() {
Response response = complete("https://app.example.com:8080@evil.example.com/phish");
assertEquals(200, response.getStatus());
assertNull(response.getLocation());
}

@Test
public void testAllowsSameOriginRedirectFromState() {
Response response = complete("https://app.example.com:8080/services/protected");
assertEquals(303, response.getStatus());
assertEquals(URI.create("https://app.example.com:8080/services/protected"), response.getLocation());
}

@Test
public void testAllowsRelativeRedirectFromState() {
Response response = complete("/services/protected");
assertEquals(303, response.getStatus());
assertEquals(URI.create("/services/protected"), response.getLocation());
}

private Response complete(String stateLocation) {
MessageContext messageContext = mock(MessageContext.class);
when(messageContext.get("http.base.path")).thenReturn(BASE_PATH);

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

OidcClientTokenContext tokenContext = mock(OidcClientTokenContext.class);
when(tokenContext.getState()).thenReturn(state);

OidcRpAuthenticationService service = new OidcRpAuthenticationService();
service.setClientTokenContextManager(mock(ClientTokenContextManager.class));
setMessageContext(service, messageContext);

return service.completeAuthentication(tokenContext);
}

private static void setMessageContext(OidcRpAuthenticationService service, MessageContext messageContext) {
try {
Field field = OidcRpAuthenticationService.class.getDeclaredField("mc");
field.setAccessible(true);
field.set(service, messageContext);
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
}
Loading