diff options
10 files changed, 284 insertions, 73 deletions
diff --git a/cps-rest/pom.xml b/cps-rest/pom.xml index 651ff18954..9e16950343 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 0000000000..943e02c273 --- /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 ca99743ca8..f38193803a 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 b9b680d35f..ef834a7a2a 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 0927c9d1e8..907528a559 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 0000000000..a700ea2132 --- /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 4e10e2c142..5ddc9d95b6 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 446a59b86d..0e07f38aa3 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 8b0202b3b2..ff1e8d0480 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 fcb4a53811..a2241bcc23 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: |