aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBruno Sakoto <bruno.sakoto@bell.ca>2021-03-03 18:27:54 -0500
committerRishi Chail <rishi.chail@est.tech>2021-03-05 08:56:30 +0000
commitf05a5032d369a9cf9052bf82ed62bad5cc4ee68b (patch)
tree15f73f5fe81b4a69f43b487b8d87d6a0ab74130d
parent50130c04626e0c5b09b344b2e11bb99c62dbf926 (diff)
Add basic authentication security
Endpoints exposed outside from the cluster require basic authentication except actuator health and info endpoints. Default user credentials are embedded in the application, they can be overridden with system environment properties. Issue-ID: CPS-175 Signed-off-by: Bruno Sakoto <bruno.sakoto@bell.ca> Change-Id: I3dfa0e49e5f4538c923e6bbe9bef976d30359fe6
-rwxr-xr-xcps-rest/pom.xml28
-rw-r--r--cps-rest/src/main/java/org/onap/cps/config/WebSecurityConfig.java61
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy96
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy66
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy18
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/controller/RestControllerSpecification.groovy34
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy30
-rw-r--r--csit/tests/cps-admin/cps-admin.robot16
-rw-r--r--csit/tests/cps-data/cps-data.robot6
-rw-r--r--docker-compose/docker-compose.yml2
10 files changed, 284 insertions, 73 deletions
diff --git a/cps-rest/pom.xml b/cps-rest/pom.xml
index 9264c6815..43875e8f4 100755
--- a/cps-rest/pom.xml
+++ b/cps-rest/pom.xml
@@ -1,3 +1,22 @@
+<!--
+ ============LICENSE_START=======================================================
+ Copyright (c) 2020 Linux Foundation.
+ Modifications Copyright (C) 2021 Bell Canada.
+ ================================================================================
+ 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.
+ ============LICENSE_END=========================================================
+-->
+
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
@@ -35,6 +54,10 @@
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ </dependency>
+ <dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
@@ -86,6 +109,11 @@
</exclusion>
</exclusions>
</dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-test</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/cps-rest/src/main/java/org/onap/cps/config/WebSecurityConfig.java b/cps-rest/src/main/java/org/onap/cps/config/WebSecurityConfig.java
new file mode 100644
index 000000000..943e02c27
--- /dev/null
+++ b/cps-rest/src/main/java/org/onap/cps/config/WebSecurityConfig.java
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (c) 2021 Bell Canada.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.config;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+/**
+ * Configuration class to implement application security.
+ * It enforces Basic Authentication access control.
+ */
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ private static final String ACTUATOR_HEALTH_PATTERN = "/manage/health/**";
+ private static final String ACTUATOR_INFO_PATTERN = "/manage/info";
+ private static final String DEFAULT_USER_NAME = "cpsuser";
+ private static final String DEFAULT_USER_PASSWORD = "cpsr0cks!";
+ private static final String USER_NAME =
+ StringUtils.defaultIfBlank(System.getenv("CPS_USERNAME"), DEFAULT_USER_NAME);
+ private static final String USER_PASSWORD =
+ StringUtils.defaultIfBlank(System.getenv("CPS_PASSWORD"), DEFAULT_USER_PASSWORD);
+ private static final String USER_ROLE = "USER";
+
+ @Override
+ protected void configure(final HttpSecurity http) throws Exception {
+ http
+ .csrf().disable()
+ .authorizeRequests()
+ .antMatchers(ACTUATOR_HEALTH_PATTERN, ACTUATOR_INFO_PATTERN).permitAll()
+ .anyRequest().authenticated()
+ .and().httpBasic();
+ }
+
+ @Override
+ protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication().withUser(USER_NAME).password("{noop}" + USER_PASSWORD).roles(USER_ROLE);
+ }
+
+}
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
index ca99743ca..f38193803 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2020 Pantheon.tech
- * Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2020, 2021 Bell Canada. All rights reserved.
* Copyright (C) 2021 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,10 +28,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.modelmapper.ModelMapper
-import org.onap.cps.api.CpsQueryService
import org.onap.cps.api.CpsAdminService
import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsModuleService
+import org.onap.cps.api.CpsQueryService
import org.onap.cps.spi.exceptions.DataspaceAlreadyDefinedException
import org.onap.cps.spi.exceptions.SchemaSetInUseException
import org.onap.cps.spi.model.Anchor
@@ -46,11 +46,10 @@ import org.springframework.mock.web.MockMultipartFile
import org.springframework.test.web.servlet.MockMvc
import org.springframework.util.LinkedMultiValueMap
import org.springframework.util.MultiValueMap
-import spock.lang.Specification
import spock.lang.Unroll
@WebMvcTest
-class AdminRestControllerSpec extends Specification {
+class AdminRestControllerSpec extends RestControllerSpecification {
@SpringBean
CpsModuleService mockCpsModuleService = Mock()
@@ -83,8 +82,12 @@ class AdminRestControllerSpec extends Specification {
given: 'an endpoint'
def createDataspaceEndpoint = "$basePath/v1/dataspaces";
when: 'post is invoked'
- def response = mvc.perform(
- post(createDataspaceEndpoint).param('dataspace-name', dataspaceName)).andReturn().response
+ def response =
+ mvc.perform(
+ post(createDataspaceEndpoint)
+ .header("Authorization", getAuthorizationHeader())
+ .param('dataspace-name', dataspaceName))
+ .andReturn().response
then: 'service method is invoked with expected parameters'
1 * mockCpsAdminService.createDataspace(dataspaceName)
and: 'dataspace is create successfully'
@@ -98,7 +101,12 @@ class AdminRestControllerSpec extends Specification {
def thrownException = new DataspaceAlreadyDefinedException("", new RuntimeException())
mockCpsAdminService.createDataspace(dataspaceName) >> { throw thrownException }
when: 'post is invoked'
- def response = mvc.perform(post(createDataspaceEndpoint).param('dataspace-name', dataspaceName)).andReturn().response
+ def response =
+ mvc.perform(
+ post(createDataspaceEndpoint)
+ .header("Authorization", getAuthorizationHeader())
+ .param('dataspace-name', dataspaceName))
+ .andReturn().response
then: 'dataspace creation fails'
response.status == HttpStatus.BAD_REQUEST.value()
}
@@ -110,8 +118,13 @@ class AdminRestControllerSpec extends Specification {
and: 'an endpoint'
def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
when: 'file uploaded with schema set create request'
- def response = mvc.perform(multipart(schemaSetEndpoint)
- .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
+ def response =
+ mvc.perform(
+ multipart(schemaSetEndpoint)
+ .file(multipartFile)
+ .header("Authorization", getAuthorizationHeader())
+ .param('schema-set-name', schemaSetName))
+ .andReturn().response
then: 'associated service method is invoked with expected parameters'
1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) >>
{ args -> yangResourceMapCapture = args[2] }
@@ -127,8 +140,13 @@ class AdminRestControllerSpec extends Specification {
and: 'an endpoint'
def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
when: 'file uploaded with schema set create request'
- def response = mvc.perform(multipart(schemaSetEndpoint)
- .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
+ def response =
+ mvc.perform(
+ multipart(schemaSetEndpoint)
+ .file(multipartFile)
+ .header("Authorization", getAuthorizationHeader())
+ .param('schema-set-name', schemaSetName))
+ .andReturn().response
then: 'associated service method is invoked with expected parameters'
1 * mockCpsModuleService.createSchemaSet(dataspaceName, schemaSetName, _) >>
{ args -> yangResourceMapCapture = args[2] }
@@ -143,8 +161,13 @@ class AdminRestControllerSpec extends Specification {
given: 'an endpoint'
def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
when: 'zip archive having #caseDescriptor is uploaded with create schema set request'
- def response = mvc.perform(multipart(schemaSetEndpoint)
- .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
+ def response =
+ mvc.perform(
+ multipart(schemaSetEndpoint)
+ .file(multipartFile)
+ .header("Authorization", getAuthorizationHeader())
+ .param('schema-set-name', schemaSetName))
+ .andReturn().response
then: 'create schema set rejected'
response.status == HttpStatus.BAD_REQUEST.value()
where: 'following cases are tested'
@@ -159,8 +182,13 @@ class AdminRestControllerSpec extends Specification {
and: 'an endpoint'
def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
when: 'file uploaded with schema set create request'
- def response = mvc.perform(multipart(schemaSetEndpoint)
- .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
+ def response =
+ mvc.perform(
+ multipart(schemaSetEndpoint)
+ .file(multipartFile)
+ .header("Authorization", getAuthorizationHeader())
+ .param('schema-set-name', schemaSetName))
+ .andReturn().response
then: 'create schema set rejected'
response.status == HttpStatus.BAD_REQUEST.value()
}
@@ -171,8 +199,13 @@ class AdminRestControllerSpec extends Specification {
def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets"
when: 'file uploaded with schema set create request'
def multipartFile = createMultipartFileForIOException(fileType)
- def response = mvc.perform(multipart(schemaSetEndpoint)
- .file(multipartFile).param('schema-set-name', schemaSetName)).andReturn().response
+ def response =
+ mvc.perform(
+ multipart(schemaSetEndpoint)
+ .file(multipartFile)
+ .header("Authorization", getAuthorizationHeader())
+ .param('schema-set-name', schemaSetName))
+ .andReturn().response
then: 'the error response returned indicating internal server error occurrence'
response.status == HttpStatus.INTERNAL_SERVER_ERROR.value()
where: 'following file types are used'
@@ -183,7 +216,9 @@ class AdminRestControllerSpec extends Specification {
given: 'an endpoint'
def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
when: 'delete schema set endpoint is invoked'
- def response = mvc.perform(delete(schemaSetEndpoint)).andReturn().response
+ def response =
+ mvc.perform(delete(schemaSetEndpoint).header("Authorization", getAuthorizationHeader()))
+ .andReturn().response
then: 'associated service method is invoked with expected parameters'
1 * mockCpsModuleService.deleteSchemaSet(dataspaceName, schemaSetName, CASCADE_DELETE_PROHIBITED)
and: 'response code indicates success'
@@ -198,7 +233,9 @@ class AdminRestControllerSpec extends Specification {
and: 'an endpoint'
def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
when: 'delete schema set endpoint is invoked'
- def response = mvc.perform(delete(schemaSetEndpoint)).andReturn().response
+ def response =
+ mvc.perform(delete(schemaSetEndpoint).header("Authorization", getAuthorizationHeader()))
+ .andReturn().response
then: 'schema set deletion fails with conflict response code'
response.status == HttpStatus.CONFLICT.value()
}
@@ -210,7 +247,9 @@ class AdminRestControllerSpec extends Specification {
and: 'an endpoint'
def schemaSetEndpoint = "$basePath/v1/dataspaces/$dataspaceName/schema-sets/$schemaSetName"
when: 'get schema set API is invoked'
- def response = mvc.perform(get(schemaSetEndpoint)).andReturn().response
+ def response =
+ mvc.perform(get(schemaSetEndpoint).header("Authorization", getAuthorizationHeader()))
+ .andReturn().response
then: 'the correct schema set is returned'
response.status == HttpStatus.OK.value()
response.getContentAsString().contains(schemaSetName)
@@ -224,8 +263,12 @@ class AdminRestControllerSpec extends Specification {
and: 'an endpoint'
def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors"
when: 'post is invoked'
- def response = mvc.perform(post(anchorEndpoint).contentType(MediaType.APPLICATION_JSON)
- .params(requestParams as MultiValueMap)).andReturn().response
+ def response =
+ mvc.perform(
+ post(anchorEndpoint).contentType(MediaType.APPLICATION_JSON)
+ .header("Authorization", getAuthorizationHeader())
+ .params(requestParams as MultiValueMap))
+ .andReturn().response
then: 'anchor is created successfully'
1 * mockCpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName)
response.status == HttpStatus.CREATED.value()
@@ -238,7 +281,9 @@ class AdminRestControllerSpec extends Specification {
and: 'an endpoint'
def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors"
when: 'get all anchors API is invoked'
- def response = mvc.perform(get(anchorEndpoint)).andReturn().response
+ def response =
+ mvc.perform(get(anchorEndpoint).header("Authorization", getAuthorizationHeader()))
+ .andReturn().response
then: 'the correct anchor is returned'
response.status == HttpStatus.OK.value()
response.getContentAsString().contains(anchorName)
@@ -250,8 +295,9 @@ class AdminRestControllerSpec extends Specification {
and: 'an endpoint'
def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName"
when: 'get anchor API is invoked'
- def response = mvc.perform(get(anchorEndpoint))
- .andReturn().response
+ def response =
+ mvc.perform(get(anchorEndpoint).header("Authorization", getAuthorizationHeader()))
+ .andReturn().response
def responseContent = response.getContentAsString()
then: 'the correct anchor is returned'
response.status == HttpStatus.OK.value()
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index b9b680d35..ef834a7a2 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
+ * Modifications Copyright (C) 2021 Bell Canada.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,10 +29,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
import org.modelmapper.ModelMapper
-import org.onap.cps.api.CpsQueryService
import org.onap.cps.api.CpsAdminService
import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsModuleService
+import org.onap.cps.api.CpsQueryService
import org.onap.cps.spi.exceptions.AnchorNotFoundException
import org.onap.cps.spi.exceptions.DataNodeNotFoundException
import org.onap.cps.spi.exceptions.DataspaceNotFoundException
@@ -45,11 +46,10 @@ import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import spock.lang.Shared
-import spock.lang.Specification
import spock.lang.Unroll
@WebMvcTest
-class DataRestControllerSpec extends Specification {
+class DataRestControllerSpec extends RestControllerSpecification {
@SpringBean
CpsDataService mockCpsDataService = Mock()
@@ -93,9 +93,12 @@ class DataRestControllerSpec extends Specification {
def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
def json = 'some json (this is not validated)'
when: 'post is invoked with datanode endpoint and json'
- def response = mvc.perform(
- post(endpoint).contentType(MediaType.APPLICATION_JSON).content(json)
- ).andReturn().response
+ def response =
+ mvc.perform(
+ post(endpoint)
+ .header("Authorization", getAuthorizationHeader())
+ .contentType(MediaType.APPLICATION_JSON).content(json))
+ .andReturn().response
then: 'a created response is returned'
response.status == HttpStatus.CREATED.value()
then: 'the java API was called with the correct parameters'
@@ -109,9 +112,9 @@ class DataRestControllerSpec extends Specification {
def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> dataNodeWithLeavesNoChildren
when: 'get request is performed through REST API'
- def response = mvc.perform(
- get(endpoint).param('xpath', xpath)
- ).andReturn().response
+ def response =
+ mvc.perform(get(endpoint).header("Authorization", getAuthorizationHeader()).param('xpath', xpath))
+ .andReturn().response
then: 'a success response is returned'
response.status == HttpStatus.OK.value()
and: 'response contains expected leaf and value'
@@ -127,10 +130,13 @@ class DataRestControllerSpec extends Specification {
def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> dataNode
when: 'get request is performed through REST API'
- def response = mvc.perform(get(endpoint)
- .param('xpath', xpath)
- .param('include-descendants', includeDescendantsOption))
- .andReturn().response
+ def response =
+ mvc.perform(
+ get(endpoint)
+ .header("Authorization", getAuthorizationHeader())
+ .param('xpath', xpath)
+ .param('include-descendants', includeDescendantsOption))
+ .andReturn().response
then: 'a success response is returned'
response.status == HttpStatus.OK.value()
and: 'the response contains child is #expectChildInResponse'
@@ -148,9 +154,9 @@ class DataRestControllerSpec extends Specification {
def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, _) >> { throw exception }
when: 'get request is performed through REST API'
- def response = mvc.perform(
- get(endpoint).param("xpath", xpath)
- ).andReturn().response
+ def response =
+ mvc.perform(get(endpoint).header("Authorization", getAuthorizationHeader()).param("xpath", xpath))
+ .andReturn().response
then: 'a success response is returned'
response.status == httpStatus.value()
where:
@@ -167,12 +173,14 @@ class DataRestControllerSpec extends Specification {
def jsonData = 'json data'
def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
when: 'patch request is performed'
- def response = mvc.perform(
- patch(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(jsonData)
- .param('xpath', xpath)
- ).andReturn().response
+ def response =
+ mvc.perform(
+ patch(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonData)
+ .header("Authorization", getAuthorizationHeader())
+ .param('xpath', xpath)
+ ).andReturn().response
then: 'the service method is invoked with expected parameters'
1 * mockCpsDataService.updateNodeLeaves(dataspaceName, anchorName, xpathServiceParameter, jsonData)
and: 'response status indicates success'
@@ -189,12 +197,14 @@ class DataRestControllerSpec extends Specification {
def jsonData = 'json data'
def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
when: 'put request is performed'
- def response = mvc.perform(
- put(endpoint)
- .contentType(MediaType.APPLICATION_JSON)
- .content(jsonData)
- .param('xpath', xpath)
- ).andReturn().response
+ def response =
+ mvc.perform(
+ put(endpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonData)
+ .header("Authorization", getAuthorizationHeader())
+ .param('xpath', xpath))
+ .andReturn().response
then: 'the service method is invoked with expected parameters'
1 * mockCpsDataService.replaceNodeTree(dataspaceName, anchorName, xpathServiceParameter, jsonData)
and: 'response status indicates success'
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
index 0927c9d1e..907528a55 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Nordix Foundation
+ * Modifications Copyright (C) 2021 Bell Canada.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,14 +20,12 @@
package org.onap.cps.rest.controller
-
import com.google.gson.Gson
import org.modelmapper.ModelMapper
import org.onap.cps.api.CpsAdminService
import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsModuleService
import org.onap.cps.api.CpsQueryService
-import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
import org.spockframework.spring.SpringBean
import org.springframework.beans.factory.annotation.Autowired
@@ -34,8 +33,6 @@ import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.HttpStatus
import org.springframework.test.web.servlet.MockMvc
-import spock.lang.Shared
-import spock.lang.Specification
import spock.lang.Unroll
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
@@ -43,7 +40,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
@WebMvcTest
-class QueryRestControllerSpec extends Specification {
+class QueryRestControllerSpec extends RestControllerSpecification {
@SpringBean
CpsDataService mockCpsDataService = Mock()
@@ -77,10 +74,13 @@ class QueryRestControllerSpec extends Specification {
and: 'the query endpoint'
def dataNodeEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query"
when: 'query data nodes API is invoked'
- def response = mvc.perform(get(dataNodeEndpoint)
- .param('cps-path', cpsPath)
- .param('include-descendants', includeDescendantsOption))
- .andReturn().response
+ def response =
+ mvc.perform(
+ get(dataNodeEndpoint)
+ .header("Authorization", getAuthorizationHeader())
+ .param('cps-path', cpsPath)
+ .param('include-descendants', includeDescendantsOption))
+ .andReturn().response
then: 'the response contains the the datanode in json format'
response.status == HttpStatus.OK.value()
response.getContentAsString().contains(new Gson().toJson(dataNode))
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/RestControllerSpecification.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/RestControllerSpecification.groovy
new file mode 100644
index 000000000..a700ea213
--- /dev/null
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/RestControllerSpecification.groovy
@@ -0,0 +1,34 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (c) 2021 Bell Canada.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+*/
+
+package org.onap.cps.rest.controller
+
+import spock.lang.Specification
+
+/**
+ * Abstract class for all rest controller specifications.
+ */
+abstract class RestControllerSpecification extends Specification {
+
+ def authorizationHeader = 'Basic Y3BzdXNlcjpjcHNyMGNrcyE='
+
+ def getAuthorizationHeader() {
+ return authorizationHeader
+ }
+
+} \ No newline at end of file
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
index 4e10e2c14..5ddc9d95b 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2020 Pantheon.tech
* Copyright (C) 2021 Nordix Foundation
+ * Modifications Copyright (C) 2021 Bell Canada.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +27,7 @@ import org.onap.cps.api.CpsAdminService
import org.onap.cps.api.CpsDataService
import org.onap.cps.api.CpsModuleService
import org.onap.cps.api.CpsQueryService
+import org.onap.cps.rest.controller.RestControllerSpecification
import org.onap.cps.spi.exceptions.AnchorAlreadyDefinedException
import org.onap.cps.spi.exceptions.CpsException
import org.onap.cps.spi.exceptions.DataInUseException
@@ -40,17 +42,17 @@ import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import spock.lang.Shared
-import spock.lang.Specification
import spock.lang.Unroll
import static org.springframework.http.HttpStatus.BAD_REQUEST
import static org.springframework.http.HttpStatus.CONFLICT
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
import static org.springframework.http.HttpStatus.NOT_FOUND
+import static org.springframework.http.HttpStatus.UNAUTHORIZED
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
@WebMvcTest
-class CpsRestExceptionHandlerSpec extends Specification {
+class CpsRestExceptionHandlerSpec extends RestControllerSpecification {
@SpringBean
CpsAdminService mockCpsAdminService = Mock()
@@ -164,6 +166,25 @@ class CpsRestExceptionHandlerSpec extends Specification {
new SchemaSetInUseException(dataspaceName, existingObjectName)]
}
+ def 'Get request without authentication is not authorized'() {
+ when: 'request is sent without authentication'
+ def response =
+ mvc.perform(get("$basePath/v1/dataspaces/dataspace-name/anchors")).andReturn().response
+ then: 'HTTP Unauthorized status code is returned'
+ assert UNAUTHORIZED.value() == response.status
+ }
+
+ def 'Get request with invalid authentication is not authorized'() {
+ when: 'request is sent with invalid authentication'
+ def response =
+ mvc.perform(
+ get("$basePath/v1/dataspaces/dataspace-name/anchors")
+ .header("Authorization", 'Basic invalid auth'))
+ .andReturn().response
+ then: 'HTTP Unauthorized status code is returned'
+ assert UNAUTHORIZED.value() == response.status
+ }
+
/*
* NB. The test uses 'get JSON by id' endpoint and associated service method invocation
* to test the exception handling. The endpoint chosen is not a subject of test.
@@ -174,7 +195,10 @@ class CpsRestExceptionHandlerSpec extends Specification {
}
def performTestRequest() {
- return mvc.perform(get("$basePath/v1/dataspaces/dataspace-name/anchors")).andReturn().response
+ return mvc.perform(
+ get("$basePath/v1/dataspaces/dataspace-name/anchors")
+ .header("Authorization", getAuthorizationHeader()))
+ .andReturn().response
}
void assertTestResponse(response, expectedStatus,
diff --git a/csit/tests/cps-admin/cps-admin.robot b/csit/tests/cps-admin/cps-admin.robot
index 446a59b86..0e07f38aa 100644
--- a/csit/tests/cps-admin/cps-admin.robot
+++ b/csit/tests/cps-admin/cps-admin.robot
@@ -9,6 +9,7 @@ Suite Setup Create Session CPS_HOST ${CPS_HOST}
*** Variables ***
+${auth} Basic Y3BzdXNlcjpjcHNyMGNrcyE=
${basePath} /cps/api
${dataspaceName} CSIT-Dataspace
${schemaSetName} CSIT-SchemaSet
@@ -18,7 +19,8 @@ ${anchorName} CSIT-Anchor
Create Dataspace
${uri}= Set Variable ${basePath}/v1/dataspaces
${params}= Create Dictionary dataspace-name=${dataspaceName}
- ${response}= POST On Session CPS_HOST ${uri} params=${params}
+ ${headers}= Create Dictionary Authorization=${auth}
+ ${response}= POST On Session CPS_HOST ${uri} params=${params} headers=${headers}
Should Be Equal As Strings ${response.status_code} 201
Create Schema Set from YANG file
@@ -27,7 +29,8 @@ Create Schema Set from YANG file
${fileData}= Get Binary File ${DATADIR}${/}test-tree.yang
${fileTuple}= Create List test.yang ${fileData} application/zip
&{files}= Create Dictionary file=${fileTuple}
- ${response}= POST On Session CPS_HOST ${uri} files=${files} params=${params}
+ ${headers}= Create Dictionary Authorization=${auth}
+ ${response}= POST On Session CPS_HOST ${uri} files=${files} params=${params} headers=${headers}
Should Be Equal As Strings ${response.status_code} 201
Create Schema Set from ZIP file
@@ -36,12 +39,14 @@ Create Schema Set from ZIP file
${fileData}= Get Binary File ${DATADIR}${/}yang-resources.zip
${fileTuple}= Create List test.zip ${fileData} application/zip
&{files}= Create Dictionary file=${fileTuple}
- ${response}= POST On Session CPS_HOST ${uri} files=${files} params=${params}
+ ${headers}= Create Dictionary Authorization=${auth}
+ ${response}= POST On Session CPS_HOST ${uri} files=${files} params=${params} headers=${headers}
Should Be Equal As Strings ${response.status_code} 201
Get Schema Set info
${uri}= Set Variable ${basePath}/v1/dataspaces/${dataspaceName}/schema-sets/${schemaSetName}
- ${response}= Get On Session CPS_HOST ${uri} expected_status=200
+ ${headers}= Create Dictionary Authorization=${auth}
+ ${response}= Get On Session CPS_HOST ${uri} headers=${headers} expected_status=200
${responseJson}= Set Variable ${response.json()}
Should Be Equal As Strings ${responseJson['name']} ${schemaSetName}
Should Be Equal As Strings ${responseJson['dataspaceName']} ${dataspaceName}
@@ -49,5 +54,6 @@ Get Schema Set info
Create Anchor
${uri}= Set Variable ${basePath}/v1/dataspaces/${dataspaceName}/anchors
${params}= Create Dictionary schema-set-name=${schemaSetName} anchor-name=${anchorName}
- ${response}= POST On Session CPS_HOST ${uri} params=${params}
+ ${headers}= Create Dictionary Authorization=${auth}
+ ${response}= POST On Session CPS_HOST ${uri} params=${params} headers=${headers}
Should Be Equal As Strings ${response.status_code} 201 \ No newline at end of file
diff --git a/csit/tests/cps-data/cps-data.robot b/csit/tests/cps-data/cps-data.robot
index 8b0202b3b..ff1e8d048 100644
--- a/csit/tests/cps-data/cps-data.robot
+++ b/csit/tests/cps-data/cps-data.robot
@@ -9,6 +9,7 @@ Suite Setup Create Session CPS_HOST ${CPS_HOST}
*** Variables ***
+${auth} Basic Y3BzdXNlcjpjcHNyMGNrcyE=
${basePath} /cps/api
${dataspaceName} CSIT-Dataspace
${anchorName} CSIT-Anchor
@@ -16,7 +17,7 @@ ${anchorName} CSIT-Anchor
*** Test Cases ***
Create Data Node
${uri}= Set Variable ${basePath}/v1/dataspaces/${dataspaceName}/anchors/${anchorName}/nodes
- ${headers} Create Dictionary Content-Type=application/json
+ ${headers} Create Dictionary Content-Type=application/json Authorization=${auth}
${jsonData}= Get Binary File ${DATADIR}${/}test-tree.json
${response}= POST On Session CPS_HOST ${uri} headers=${headers} data=${jsonData}
Should Be Equal As Strings ${response.status_code} 201
@@ -24,7 +25,8 @@ Create Data Node
Get Data Node by XPath
${uri}= Set Variable ${basePath}/v1/dataspaces/${dataspaceName}/anchors/${anchorName}/node
${params}= Create Dictionary xpath=/test-tree/branch[@name='Left']/nest
- ${response}= Get On Session CPS_HOST ${uri} params=${params} expected_status=200
+ ${headers}= Create Dictionary Authorization=${auth}
+ ${response}= Get On Session CPS_HOST ${uri} params=${params} headers=${headers} expected_status=200
${responseJson}= Set Variable ${response.json()}
Should Be Equal As Strings ${responseJson['name']} Small
diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml
index fcb4a5381..a2241bcc2 100644
--- a/docker-compose/docker-compose.yml
+++ b/docker-compose/docker-compose.yml
@@ -20,7 +20,7 @@ version: "3.7"
services:
#cps-standalone:
# container_name: cps-service
- # image: ps-service:${VERSION}
+ # image: cps-service:${VERSION}
# volumes:
# - "./application.yml:/app/resources/application.yml"
# ports: