From 47c294d8a0e7e250e555523318337b39b8a074f6 Mon Sep 17 00:00:00 2001 From: danielhanrahan Date: Fri, 1 Mar 2024 10:56:19 +0000 Subject: [PATCH] Integration test of Bearer Token pass-through (CPS-2126 #5) This covers REST endpoints of GET, POST, PUT, PATCH, DELETE of /ncmp/v1/ch/{cmHandleId}/data/ds/{datastoreName} and the async REST endpoint of POST /ncmp/v1/data It verifies that: - bearer token is passed from NCMP to DMI - basic auth header is not passed from NCMP to DMI Issue-ID: CPS-2137 Signed-off-by: danielhanrahan Change-Id: Ie4761a848904175a9d8cd5b917817e85f5b69813 --- .../integration/base/CpsIntegrationSpecBase.groovy | 8 +- .../NcmpBearerTokenPassthroughSpec.groovy | 124 +++++++++++++++++++++ .../src/test/resources/application.yml | 2 +- 3 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy index 23504e49c..1577524f2 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy @@ -41,12 +41,14 @@ import org.onap.cps.spi.utils.SessionManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.annotation.ComponentScan import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.test.web.client.MockRestServiceServer +import org.springframework.test.web.servlet.MockMvc import org.springframework.web.client.RestTemplate import org.testcontainers.spock.Testcontainers import spock.lang.Shared @@ -56,9 +58,10 @@ import spock.util.concurrent.PollingConditions import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus -@SpringBootTest(classes = [CpsDataspaceService]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = [CpsDataspaceService]) @Testcontainers @EnableAutoConfiguration +@AutoConfigureMockMvc @EnableJpaRepositories(basePackageClasses = [DataspaceRepository]) @ComponentScan(basePackages = ['org.onap.cps']) @EntityScan('org.onap.cps.spi.entities') @@ -67,6 +70,9 @@ abstract class CpsIntegrationSpecBase extends Specification { @Shared DatabaseTestContainer databaseTestContainer = DatabaseTestContainer.getInstance() + @Autowired + MockMvc mvc; + @Autowired CpsDataspaceService cpsDataspaceService diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy new file mode 100644 index 000000000..0dabbf30a --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy @@ -0,0 +1,124 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * 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.integration.functional + +import java.time.Duration +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.client.match.MockRestRequestMatchers + +import static org.springframework.http.HttpMethod.GET +import static org.springframework.http.HttpMethod.DELETE +import static org.springframework.http.HttpMethod.PATCH +import static org.springframework.http.HttpMethod.POST +import static org.springframework.http.HttpMethod.PUT +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { + + static final NO_MODULE_SET_TAG = '' + static final MODULE_REFERENCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json') + static final MODULE_RESOURCES_RESPONSE = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json') + + def setup() { + registerCmHandle(DMI_URL, 'ch-1', NO_MODULE_SET_TAG, MODULE_REFERENCES_RESPONSE, MODULE_RESOURCES_RESPONSE) + } + + def cleanup() { + deregisterCmHandle(DMI_URL, 'ch-1') + } + + def 'Bearer token is passed from NCMP to DMI in pass-through data operations.'() { + given: 'DMI will expect to receive a request with a bearer token' + def targetDmiUrl = "$DMI_URL/dmi/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=my-resource-id" + mockDmiServer.expect(requestTo(targetDmiUrl)) + .andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, 'Bearer some-bearer-token')) + .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) + + when: 'a pass-through data request is sent to NCMP with a bearer token' + mvc.perform(request(httpMethod, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running') + .queryParam('resourceIdentifier', 'my-resource-id') + .contentType(MediaType.APPLICATION_JSON) + .content('{ "some-json": "data" }') + .header(HttpHeaders.AUTHORIZATION, 'Bearer some-bearer-token')) + .andExpect(status().is2xxSuccessful()) + + then: 'DMI has received request with bearer token' + mockDmiServer.verify() + + where: 'all HTTP operations are applied' + httpMethod << [GET, POST, PUT, PATCH, DELETE] + } + + def 'Basic auth header is NOT passed from NCMP to DMI in pass-through data operations.'() { + given: 'DMI will expect to receive a request with no authorization header' + def targetDmiUrl = "$DMI_URL/dmi/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=my-resource-id" + mockDmiServer.expect(requestTo(targetDmiUrl)) + .andExpect(MockRestRequestMatchers.headerDoesNotExist(HttpHeaders.AUTHORIZATION)) + .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) + + when: 'a pass-through data request is sent to NCMP with basic authentication' + mvc.perform(request(httpMethod, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running') + .queryParam('resourceIdentifier', 'my-resource-id') + .contentType(MediaType.APPLICATION_JSON) + .content('{ "some-json": "data" }') + .header(HttpHeaders.AUTHORIZATION, 'Basic Y3BzdXNlcjpjcHNyMGNrcyE=')) + .andExpect(status().is2xxSuccessful()) + + then: 'DMI has received request with no authorization header' + mockDmiServer.verify() + + where: 'all HTTP operations are applied' + httpMethod << [GET, POST, PUT, PATCH, DELETE] + } + + def 'Bearer token is passed from NCMP to DMI in async batch pass-through data operation.'() { + given: 'DMI will expect to receive a request with a bearer token' + mockDmiServer.expect(method(POST)) + .andExpect(MockRestRequestMatchers.header(HttpHeaders.AUTHORIZATION, 'Bearer some-bearer-token')) + .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON)) + + when: 'a pass-through async data request is sent to NCMP with a bearer token' + def requestBody = """{"operations": [{ + "operation": "read", + "operationId": "operational-1", + "datastore": "ncmp-datastore:passthrough-running", + "resourceIdentifier": "my-resource-id", + "targetIds": ["ch-1"] + }]}""" + mvc.perform(request(POST, '/ncmp/v1/data') + .queryParam('topic', 'my-topic') + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .header(HttpHeaders.AUTHORIZATION, 'Bearer some-bearer-token')) + .andExpect(status().is2xxSuccessful()) + + then: 'DMI will receive the async request with bearer token' + mockDmiServer.verify(Duration.ofSeconds(1)) + } + +} diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml index f77cb02f7..3d61bdbea 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -112,7 +112,7 @@ app: topic: ${DMI_DEVICE_HEARTBEAT_TOPIC:dmi-device-heartbeat} notification: - enabled: false + enabled: true async: executor: core-pool-size: 2 -- 2.16.6