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 <[email protected]>
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";>

Reply via email to