This is an automated email from the ASF dual-hosted git repository.
coheigea 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 3fa2ffd04bc Disallow a dynamic registration client from requesting
scopes without validation (#3297)
3fa2ffd04bc is described below
commit 3fa2ffd04bc814d2fa9ab723d9b6c7f9fad35ed8
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Jul 10 10:39:34 2026 +0100
Disallow a dynamic registration client from requesting scopes without
validation (#3297)
---
.../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">