Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.cxf.rs.security.oauth2.services;

import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -59,8 +60,11 @@

@Path("register")
public class DynamicRegistrationService {
private static final List<String> LOOPBACK_HOSTS = List.of("localhost", "127.0.0.1", "[::1]");
private static final String WEB_APPLICATION_TYPE = "web";
private static final String NATIVE_APPLICATION_TYPE = "native";
private static final String INVALID_CLIENT_METADATA = "invalid_client_metadata";
private static final String DEFAULT_APPLICATION_TYPE = "web";
private static final String DEFAULT_APPLICATION_TYPE = WEB_APPLICATION_TYPE;
private static final Integer DEFAULT_CLIENT_ID_SIZE = 10;
private ClientRegistrationProvider clientProvider;
private String initialAccessToken;
Expand Down Expand Up @@ -187,7 +191,7 @@ protected ClientRegistration fromClientToClientRegistration(Client c) {
ClientRegistration reg = new ClientRegistration();
reg.setClientName(c.getApplicationName());
reg.setGrantTypes(c.getAllowedGrantTypes());
reg.setApplicationType(c.isConfidential() ? "web" : "native");
reg.setApplicationType(c.isConfidential() ? WEB_APPLICATION_TYPE : NATIVE_APPLICATION_TYPE);
if (!c.getRedirectUris().isEmpty()) {
reg.setRedirectUris(c.getRedirectUris());
}
Expand Down Expand Up @@ -421,12 +425,48 @@ protected boolean isPasswordRequired(List<String> grantTypes, String tokenEndpoi
|| OAuthConstants.TOKEN_ENDPOINT_AUTH_POST.equals(tokenEndpointAuthMethod));
}

@SuppressWarnings("PMD.CollapsibleIfStatements")
protected void validateRequestUri(String uri, String appType, List<String> grantTypes) {
if (uri == null || uri.isBlank()) {
reportInvalidRequestError(new OAuthError(INVALID_CLIENT_METADATA, "Empty redirect URI is not supported"));
}

// Web Clients using the OAuth Implicit Grant Type MUST only register URLs using the https scheme
// as redirect_uris; they MUST NOT use localhost as the hostname. Native Clients MUST only register
// redirect_uris using custom URI schemes or URLs using the http: scheme with localhost as the hostname.
// redirect_uris using custom URI schemes or loopback URLs using the http scheme; loopback URLs use
// localhost or the IP loopback literals 127.0.0.1 or [::1] as the hostname.
// Authorization Servers MAY place additional constraints on Native Clients. Authorization Servers MAY
// reject Redirection URI values using the http scheme, other than the localhost case for Native Clients

final URI parsedUri = URI.create(uri);
if (parsedUri.getHost() == null || parsedUri.getScheme() == null) {
reportInvalidRequestError(new OAuthError(INVALID_CLIENT_METADATA, "Unsupported redirect URI scheme/host"));
}
final String host = parsedUri.getHost().toLowerCase();
final String scheme = parsedUri.getScheme().toLowerCase();
if (!scheme.equals("http") && !scheme.equals("https")) {
reportInvalidRequestError(new OAuthError(INVALID_CLIENT_METADATA,
"Redirect URI scheme is not allowed: " + scheme
+ ". Allowed schemes: http, https"));
}
Comment thread
reta marked this conversation as resolved.

// Kind of the application. The default, if omitted, is web. The defined values are native or web.
if (appType == null || appType.isBlank() || appType.equalsIgnoreCase(WEB_APPLICATION_TYPE)) {
if (grantTypes.contains(OAuthConstants.IMPLICIT_GRANT)) {
if (!"https".equals(scheme)) {
reportInvalidRequestError(new OAuthError(INVALID_CLIENT_METADATA,
"Unsupported redirect URI scheme"));
} else if ("localhost".equals(host)) {
reportInvalidRequestError(new OAuthError(INVALID_CLIENT_METADATA,
"Unsupported redirect URI hostname"));
}
}
} else if (appType.equalsIgnoreCase(NATIVE_APPLICATION_TYPE)) {
if ("http".equals(scheme) && !LOOPBACK_HOSTS.contains(host)) {
reportInvalidRequestError(new OAuthError(INVALID_CLIENT_METADATA,
"Unsupported redirect URI hostname for scheme"));
}
}
}

public void setClientProvider(ClientRegistrationProvider clientProvider) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.security.auth.x500.X500Principal;

Expand Down Expand Up @@ -89,6 +90,82 @@ public void testAcceptsRegisteredScopesWhenAllowlistNotConfigured() {
assertEquals(Collections.singletonList("openid"), client.getRegisteredScopes());
}

@Test
public void testAcceptsAllowedRedirectUrlsWebApp() {
TestDynamicRegistrationService service = new TestDynamicRegistrationService();

ClientRegistration request = new ClientRegistration();
request.setScope("read write");
request.setRedirectUris(List.of("https://localhost", "http://localhost"));

Client client = createClient();
service.applyClientRegistration(request, client);

assertEquals(Arrays.asList("https://localhost", "http://localhost"), client.getRedirectUris());
}

@Test
public void testRejectsNotAllowedRedirectUrlsWebApp() {
TestDynamicRegistrationService service = new TestDynamicRegistrationService();

final List<String> schemes = List.of("http", "https");
for (String scheme: schemes) {
ClientRegistration request = new ClientRegistration();
request.setScope("read write");
request.setRedirectUris(List.of(scheme + "://localhost"));

Client client = createClient();
client.setAllowedGrantTypes(Collections.singletonList(OAuthConstants.IMPLICIT_GRANT));
assertThrows(BadRequestException.class, () -> service.applyClientRegistration(request, client));
}
}

@Test
public void testAcceptsAllowedRedirectUrlsNativeApp() {
TestDynamicRegistrationService service = new TestDynamicRegistrationService();

final List<String> hosts = List.of("localhost", "127.0.0.1", "[::1]");
for (String host: hosts) {
ClientRegistration request = new ClientRegistration();
request.setScope("read write");
request.setRedirectUris(List.of("http://" + host));
request.setApplicationType("native");

Client client = createClient();
service.applyClientRegistration(request, client);

assertEquals(Arrays.asList("http://" + host), client.getRedirectUris());
}
}

@Test
public void testRejectsNotAllowedRedirectUrlsNativeApp() {
TestDynamicRegistrationService service = new TestDynamicRegistrationService();

ClientRegistration request = new ClientRegistration();
request.setScope("read write");
request.setRedirectUris(List.of("http://test"));
request.setApplicationType("native");

Client client = createClient();
assertThrows(BadRequestException.class, () -> service.applyClientRegistration(request, client));
}

@Test
public void testRejectsNotAllowedRedirectUrls() {
TestDynamicRegistrationService service = new TestDynamicRegistrationService();

final List<String> uris = List.of("custom://test", "//test", "http:/");
for (String uri: uris) {
ClientRegistration request = new ClientRegistration();
request.setScope("read write");
request.setRedirectUris(List.of(uri));

Client client = createClient();
assertThrows(BadRequestException.class, () -> service.applyClientRegistration(request, client));
}
}

@Test
public void testRejectsTlsClientAuthWithoutTlsCertificate() {
TestDynamicRegistrationService service = new TestDynamicRegistrationService();
Expand Down
Loading