You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2026/07/09 14:45:17 UTC
(cxf) 01/01: Disallow a dynamic registration client from requesting scopes without validation
This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch coheigea/dynamic-registration
in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 89d2346dd2812387acddba5d29fb41f52ceafdca
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Jul 9 15:44:40 2026 +0100
Disallow a dynamic registration client from requesting scopes without validation
---
.../services/DynamicRegistrationService.java | 30 ++++++-
.../services/DynamicRegistrationServiceTest.java | 94 ++++++++++++++++++++++
.../jaxrs/security/oauth2/tls/serverTls-fips.xml | 5 ++
.../jaxrs/security/oauth2/tls/serverTls.xml | 5 ++
4 files changed, 133 insertions(+), 1 deletion(-)
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 e9b46b53692..eb0e387a744 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
@@ -19,6 +19,7 @@
package org.apache.cxf.rs.security.oauth2.services;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import jakarta.ws.rs.Consumes;
@@ -44,6 +45,7 @@ import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.rs.security.oauth2.common.Client;
import org.apache.cxf.rs.security.oauth2.common.OAuthError;
import org.apache.cxf.rs.security.oauth2.common.UserSubject;
+import org.apache.cxf.rs.security.oauth2.provider.AbstractOAuthDataProvider;
import org.apache.cxf.rs.security.oauth2.provider.ClientRegistrationProvider;
import org.apache.cxf.rs.security.oauth2.utils.AuthorizationUtils;
import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
@@ -52,6 +54,7 @@ import org.apache.cxf.rt.security.crypto.CryptoUtils;
@Path("register")
public class DynamicRegistrationService {
+ private static final String INVALID_CLIENT_METADATA = "invalid_client_metadata";
private static final String DEFAULT_APPLICATION_TYPE = "web";
private static final Integer DEFAULT_CLIENT_ID_SIZE = 10;
private ClientRegistrationProvider clientProvider;
@@ -60,6 +63,7 @@ public class DynamicRegistrationService {
private MessageContext mc;
private boolean supportRegistrationAccessTokens = true;
private String userRole;
+ private List<String> allowedClientScopes;
@POST
@Consumes("application/json")
@@ -328,7 +332,9 @@ public class DynamicRegistrationService {
// Client Scopes
String scope = request.getScope();
if (!StringUtils.isEmpty(scope)) {
- client.setRegisteredScopes(OAuthUtils.parseScope(scope));
+ List<String> requestedScopes = OAuthUtils.parseScope(scope);
+ validateClientScopes(requestedScopes);
+ client.setRegisteredScopes(requestedScopes);
}
// Client Application URI
String clientUri = request.getClientUri();
@@ -417,6 +423,28 @@ public class DynamicRegistrationService {
this.userRole = userRole;
}
+ public void setAllowedClientScopes(List<String> allowedClientScopes) {
+ this.allowedClientScopes = allowedClientScopes;
+ }
+
+ protected void validateClientScopes(List<String> requestedScopes) {
+ if (requestedScopes == null || requestedScopes.isEmpty()) {
+ return;
+ }
+
+ List<String> allowedScopes = allowedClientScopes;
+ if (allowedScopes == null && clientProvider instanceof AbstractOAuthDataProvider) {
+ allowedScopes = new java.util.ArrayList<>(
+ ((AbstractOAuthDataProvider)clientProvider).getPermissionMap().keySet());
+ }
+
+ if (allowedScopes != null && !new HashSet<>(allowedScopes).containsAll(requestedScopes)) {
+ OAuthError error =
+ new OAuthError(INVALID_CLIENT_METADATA, "Invalid scope metadata");
+ reportInvalidRequestError(error);
+ }
+ }
+
private void reportInvalidRequestError(OAuthError entity) {
reportInvalidRequestError(entity, MediaType.APPLICATION_JSON_TYPE);
}
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
new file mode 100644
index 00000000000..72fddf6516a
--- /dev/null
+++ b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationServiceTest.java
@@ -0,0 +1,94 @@
+/**
+ * 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.oauth2.services;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import jakarta.ws.rs.BadRequestException;
+import org.apache.cxf.rs.security.oauth2.common.Client;
+import org.apache.cxf.rs.security.oauth2.common.OAuthError;
+import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+public class DynamicRegistrationServiceTest {
+
+ @Test
+ public void testRejectsUnallowedRegisteredScope() {
+ TestDynamicRegistrationService service = new TestDynamicRegistrationService();
+ service.setAllowedClientScopes(Collections.singletonList("read"));
+
+ ClientRegistration request = new ClientRegistration();
+ request.setScope("admin read");
+
+ Client client = createClient();
+
+ BadRequestException ex = assertThrows(BadRequestException.class,
+ () -> service.applyClientRegistration(request, client));
+
+ assertNotNull(ex.getResponse());
+ OAuthError error = (OAuthError)ex.getResponse().getEntity();
+ assertNotNull(error);
+ assertEquals("invalid_client_metadata", error.getError());
+ }
+
+ @Test
+ public void testAcceptsAllowedRegisteredScopes() {
+ TestDynamicRegistrationService service = new TestDynamicRegistrationService();
+ service.setAllowedClientScopes(Arrays.asList("read", "write"));
+
+ ClientRegistration request = new ClientRegistration();
+ request.setScope("read write");
+
+ Client client = createClient();
+ service.applyClientRegistration(request, client);
+
+ assertEquals(Arrays.asList("read", "write"), client.getRegisteredScopes());
+ }
+
+ @Test
+ public void testAcceptsRegisteredScopesWhenAllowlistNotConfigured() {
+ TestDynamicRegistrationService service = new TestDynamicRegistrationService();
+
+ ClientRegistration request = new ClientRegistration();
+ request.setScope("openid");
+
+ Client client = createClient();
+ service.applyClientRegistration(request, client);
+
+ assertEquals(Collections.singletonList("openid"), client.getRegisteredScopes());
+ }
+
+ private static Client createClient() {
+ Client client = new Client("client", "secret", true);
+ client.setAllowedGrantTypes(Collections.singletonList(OAuthConstants.CLIENT_CREDENTIALS_GRANT));
+ return client;
+ }
+
+ private static final class TestDynamicRegistrationService extends DynamicRegistrationService {
+ void applyClientRegistration(ClientRegistration request, Client client) {
+ fromClientRegistrationToClient(request, client);
+ }
+ }
+}
diff --git a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/tls/serverTls-fips.xml b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/tls/serverTls-fips.xml
index b46fcc28afa..15cc2a92df8 100644
--- a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/tls/serverTls-fips.xml
+++ b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/tls/serverTls-fips.xml
@@ -107,6 +107,11 @@ under the License.
<bean id="dynRegServiceWithAt" class="org.apache.cxf.rs.security.oauth2.services.DynamicRegistrationService">
<property name="clientProvider" ref="dataProviderJwt"/>
<property name="initialAccessToken" value="123456789"/>
+ <property name="allowedClientScopes">
+ <list>
+ <value>openid</value>
+ </list>
+ </property>
</bean>
<jaxrs:server id="tokenServer1" address="https://localhost:${testutil.ports.jaxrs-oauth2-tls}/oauth2">
diff --git a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/tls/serverTls.xml b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/tls/serverTls.xml
index 748036ac1e7..b9dca9da8e4 100644
--- a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/tls/serverTls.xml
+++ b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/oauth2/tls/serverTls.xml
@@ -109,6 +109,11 @@ under the License.
<bean id="dynRegServiceWithAt" class="org.apache.cxf.rs.security.oauth2.services.DynamicRegistrationService">
<property name="clientProvider" ref="dataProviderJwt"/>
<property name="initialAccessToken" value="123456789"/>
+ <property name="allowedClientScopes">
+ <list>
+ <value>openid</value>
+ </list>
+ </property>
</bean>
<jaxrs:server id="tokenServer1" address="https://localhost:${testutil.ports.jaxrs-oauth2-tls}/oauth2">