Added JettyConfig with support for ambiguous path separators 26/140926/4
authorsourabh_sourabh <sourabh.sourabh@est.tech>
Wed, 21 May 2025 11:54:15 +0000 (12:54 +0100)
committersourabh_sourabh <sourabh.sourabh@est.tech>
Wed, 21 May 2025 14:42:38 +0000 (15:42 +0100)
- Added JettyConfig to customize Jetty's HttpConfiguration
- For ambiguous path separators via UriCompliance settings.

Issue-ID: CPS-2819
Change-Id: I30cbb14cf31ac808a9a9761cf8dbf9ba0df870ae
Signed-off-by: sourabh_sourabh <sourabh.sourabh@est.tech>
cps-application/src/main/java/org/onap/cps/config/JettyConfig.java [new file with mode: 0644]
cps-application/src/test/groovy/org/onap/cps/config/JettyConfigSpec.groovy [new file with mode: 0644]
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy

diff --git a/cps-application/src/main/java/org/onap/cps/config/JettyConfig.java b/cps-application/src/main/java/org/onap/cps/config/JettyConfig.java
new file mode 100644 (file)
index 0000000..a080ce6
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.config;
+
+import java.util.EnumSet;
+import org.eclipse.jetty.http.UriCompliance;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Configures the Jetty server to allow encoded slashes (%2F) within URI path segments.
+
+ * This customization is essential when path parameters may include encoded slashes,
+ * such as hierarchical identifiers (e.g., {@code SubNetwork=Europe/SubNetwork=Ireland}).
+ * By permitting the {@code AMBIGUOUS_PATH_SEPARATOR} violation, Jetty accepts these
+ * encoded slashes without rejecting the request.
+ *
+ * @see <a href="https://jetty.org/docs/jetty/12/programming-guide/server/compliance.html">Jetty Server Compliance Modes</a>
+ * @see <a href="https://javadoc.jetty.org/jetty-12/org/eclipse/jetty/http/UriCompliance.Violation.html">UriCompliance.Violation</a>
+ */
+@Configuration
+public class JettyConfig implements WebServerFactoryCustomizer<JettyServletWebServerFactory> {
+
+    /**
+     * Customizes the Jetty server factory to allow encoded slashes in URI paths.
+     *
+     * @param jettyServletWebServerFactory the Jetty servlet web server factory to customize
+     */
+    @Override
+    public void customize(final JettyServletWebServerFactory jettyServletWebServerFactory) {
+        jettyServletWebServerFactory.addServerCustomizers(server -> {
+            for (final Connector connector : server.getConnectors()) {
+                for (final ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
+                    if (connectionFactory instanceof HttpConnectionFactory) {
+                        final HttpConfiguration httpConfiguration
+                                = ((HttpConnectionFactory) connectionFactory).getHttpConfiguration();
+                        httpConfiguration.setUriCompliance(UriCompliance.from(EnumSet.of(
+                                UriCompliance.Violation.AMBIGUOUS_PATH_SEPARATOR)));
+                    }
+                }
+            }
+        });
+    }
+}
+
diff --git a/cps-application/src/test/groovy/org/onap/cps/config/JettyConfigSpec.groovy b/cps-application/src/test/groovy/org/onap/cps/config/JettyConfigSpec.groovy
new file mode 100644 (file)
index 0000000..f85a879
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.config
+
+import org.eclipse.jetty.server.ConnectionFactory
+import org.eclipse.jetty.server.Connector
+import org.eclipse.jetty.server.HttpConfiguration
+import org.eclipse.jetty.server.HttpConnectionFactory
+import org.eclipse.jetty.server.Server
+import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory
+import spock.lang.Specification
+
+class JettyConfigSpec extends Specification {
+
+    def objectUnderTest = new JettyConfig()
+    def server = Mock(Server)
+    def connector = Mock(Connector)
+
+    def 'Enable support for ambiguous path separators in Jetty Http configuration'() {
+        given: 'a Jetty server factory'
+            def jettyServletWebServerFactory = new JettyServletWebServerFactory()
+        and: 'a mocked connection factory (Http or Non-Http)'
+            def connectionFactory = connectionFactoryType == 'http' ? Mock(HttpConnectionFactory) : Mock(ConnectionFactory)
+        and: 'optional mock for HttpConfiguration if applicable'
+            def httpConfig = null
+            if (connectionFactory instanceof HttpConnectionFactory) {
+                httpConfig = Mock(HttpConfiguration)
+                connectionFactory.getHttpConfiguration() >> httpConfig
+            }
+        and: 'mocked components return expected values'
+            connector.getConnectionFactories() >> [connectionFactory]
+            server.getConnectors() >> [connector]
+        when: 'JettyConfig customization is triggered on the server factory'
+            objectUnderTest.customize(jettyServletWebServerFactory)
+        and: 'a server customizer is extracted from the configured factory'
+            def serverCustomizer = jettyServletWebServerFactory.serverCustomizers.first()
+        and: 'the customizer is applied to the mocked Jetty server'
+            serverCustomizer.customize(server)
+        then: 'only the HTTP configuration is updated to allow ambiguous path separators'
+            expectedCalls * httpConfig.setUriCompliance({ it.toString().contains('AMBIGUOUS_PATH_SEPARATOR') })
+        where: 'type of connection factories'
+            scenario                                                 | connectionFactoryType || expectedCalls
+            'HttpConnectionFactory - should configure UriCompliance' | 'http'                || 1
+            'Non-HttpConnectionFactory - should do nothing'          | 'non-http'            || 0
+    }
+}
index e934530..8c7f5fb 100644 (file)
@@ -477,6 +477,17 @@ class NetworkCmProxyControllerSpec extends Specification {
             'invalid datastore'     | 'DELETE'  | 'invalid'
     }
 
+    def 'Ensure URI-encoded alternateId with slashes is accepted for #operation - #scenario'() {
+        given: 'A URI-encoded alternateId that includes slashes'
+            def alternateIdWithSlashes = '/some/cps/path'
+            def encodedAlternateId = URLEncoder.encode(alternateIdWithSlashes, 'UTF-8')
+            def url = "$ncmpBasePathV1/ch/${encodedAlternateId}/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=some-value"
+        when: 'A passthrough operation is executed on the URL containing the encoded alternateId'
+            def response = mvc.perform(executeRestOperation('POST', url)).andReturn().response
+        then: 'The API successfully processes the request and returns the expected HTTP status'
+            assert response.status == HttpStatus.CREATED.value()
+    }
+
     def executeRestOperation(operation, url) {
         if (operation == 'POST') {
             return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)