This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new 4a2ec3445fb Validate redirect URIs according to OpenID Connect Dynamic
Client Registration 1.0 specification (#3318)
4a2ec3445fb is described below
commit 4a2ec3445fb450d20817fcf98b9e0fb3ef4e9259
Author: Andriy Redko <[email protected]>
AuthorDate: Tue Jul 21 17:08:49 2026 -0400
Validate redirect URIs according to OpenID Connect Dynamic Client
Registration 1.0 specification (#3318)
* Validate redirect URIs according to OpenID Connect Dynamic Client
Registration 1.0 specification
* Address code review comments
---
.../services/DynamicRegistrationService.java | 46 ++++++++++++-
.../services/DynamicRegistrationServiceTest.java | 77 ++++++++++++++++++++++
2 files changed, 120 insertions(+), 3 deletions(-)
diff --git
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationService.java
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationService.java
index 8be6b6d9bda..ecb4847e8ad 100644
---
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationService.java
+++
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationService.java
@@ -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;
@@ -59,8 +60,11 @@ import org.apache.cxf.security.transport.TLSSessionInfo;
@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;
@@ -187,7 +191,7 @@ public class DynamicRegistrationService {
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());
}
@@ -421,12 +425,48 @@ public class DynamicRegistrationService {
||
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"));
+ }
+
+ // 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) {
diff --git
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationServiceTest.java
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationServiceTest.java
index fa339c5b11c..3bfaceafffa 100644
---
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationServiceTest.java
+++
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationServiceTest.java
@@ -22,6 +22,7 @@ import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import javax.security.auth.x500.X500Principal;
@@ -89,6 +90,82 @@ public class DynamicRegistrationServiceTest {
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();