diff options
author | Bruno Sakoto <bruno.sakoto@bell.ca> | 2021-03-03 18:27:54 -0500 |
---|---|---|
committer | Rishi Chail <rishi.chail@est.tech> | 2021-03-05 08:56:30 +0000 |
commit | f05a5032d369a9cf9052bf82ed62bad5cc4ee68b (patch) | |
tree | 15f73f5fe81b4a69f43b487b8d87d6a0ab74130d /cps-rest | |
parent | 50130c04626e0c5b09b344b2e11bb99c62dbf926 (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
Diffstat (limited to 'cps-rest')
7 files changed, 268 insertions, 65 deletions
diff --git a/cps-rest/pom.xml b/cps-rest/pom.xml index 9264c68150..43875e8f46 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, |