From 89d2346dd2812387acddba5d29fb41f52ceafdca Mon Sep 17 00:00:00 2001 From: Colm O hEigeartaigh Date: Thu, 9 Jul 2026 15:44:40 +0100 Subject: [PATCH] Disallow a dynamic registration client from requesting scopes without validation --- .../services/DynamicRegistrationService.java | 30 +++++- .../DynamicRegistrationServiceTest.java | 94 +++++++++++++++++++ .../security/oauth2/tls/serverTls-fips.xml | 5 + .../jaxrs/security/oauth2/tls/serverTls.xml | 5 + 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationServiceTest.java 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.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 @@ @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 allowedClientScopes; @POST @Consumes("application/json") @@ -328,7 +332,9 @@ protected void fromClientRegistrationToClient(ClientRegistration request, Client // Client Scopes String scope = request.getScope(); if (!StringUtils.isEmpty(scope)) { - client.setRegisteredScopes(OAuthUtils.parseScope(scope)); + List requestedScopes = OAuthUtils.parseScope(scope); + validateClientScopes(requestedScopes); + client.setRegisteredScopes(requestedScopes); } // Client Application URI String clientUri = request.getClientUri(); @@ -417,6 +423,28 @@ public void setUserRole(String userRole) { this.userRole = userRole; } + public void setAllowedClientScopes(List allowedClientScopes) { + this.allowedClientScopes = allowedClientScopes; + } + + protected void validateClientScopes(List requestedScopes) { + if (requestedScopes == null || requestedScopes.isEmpty()) { + return; + } + + List 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. + + + openid + + 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. + + + openid + +