summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiamh Core <niamh.core@est.tech>2021-08-23 14:40:55 +0000
committerGerrit Code Review <gerrit@onap.org>2021-08-23 14:40:55 +0000
commitf2f0a770d2e4e9dde29c4e77c18fedd70331b5e7 (patch)
tree356975d0bd24bac5d0357c1bc46c8bd2af0490f2
parent2a41a4f022af65ab878f6e6f7646e78d4ef27a06 (diff)
parent743380d1f171d4c0dd46dc0cd5b47d8ea93bea44 (diff)
Merge "Add basic security to query interface"
-rwxr-xr-xpom.xml9
-rw-r--r--src/main/java/org/onap/cps/temporal/controller/rest/QueryController.java166
-rw-r--r--src/main/java/org/onap/cps/temporal/controller/rest/QueryResponseFactory.java170
-rw-r--r--src/main/java/org/onap/cps/temporal/controller/rest/config/WebSecurityConfig.java80
-rw-r--r--src/main/java/org/onap/cps/temporal/controller/rest/model/SortMapper.java2
-rwxr-xr-xsrc/main/resources/application.yml6
-rw-r--r--src/test/groovy/org/onap/cps/temporal/controller/rest/ControllerSecuritySpec.groovy99
-rw-r--r--src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy10
-rw-r--r--src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy6
-rw-r--r--src/test/resources/application.yml9
10 files changed, 372 insertions, 185 deletions
diff --git a/pom.xml b/pom.xml
index e5f5396..6664f42 100755
--- a/pom.xml
+++ b/pom.xml
@@ -92,6 +92,10 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
@@ -163,6 +167,11 @@
</exclusions>
</dependency>
<dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
diff --git a/src/main/java/org/onap/cps/temporal/controller/rest/QueryController.java b/src/main/java/org/onap/cps/temporal/controller/rest/QueryController.java
index ab29e19..da1a9ea 100644
--- a/src/main/java/org/onap/cps/temporal/controller/rest/QueryController.java
+++ b/src/main/java/org/onap/cps/temporal/controller/rest/QueryController.java
@@ -20,22 +20,32 @@
package org.onap.cps.temporal.controller.rest;
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
+
import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.ValidationException;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
+import org.onap.cps.temporal.controller.rest.model.AnchorDetails;
+import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapper;
import org.onap.cps.temporal.controller.rest.model.AnchorHistory;
import org.onap.cps.temporal.controller.rest.model.SortMapper;
import org.onap.cps.temporal.controller.utils.DateTimeUtility;
import org.onap.cps.temporal.domain.NetworkData;
import org.onap.cps.temporal.domain.SearchCriteria;
import org.onap.cps.temporal.service.NetworkDataService;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.util.UriComponentsBuilder;
@RestController
@RequestMapping("${rest.api.base-path}")
@@ -48,16 +58,18 @@ public class QueryController implements CpsTemporalQueryApi {
/**
* Constructor.
*
- * @param networkDataService networkDataService
- * @param sortMapper sortMapper
- * @param queryResponseFactory anchorHistoryResponseFactory
+ * @param networkDataService networkDataService
+ * @param sortMapper sortMapper
+ * @param anchorDetailsMapper anchorDetailsMapper
+ * @param basePath basePath
*/
public QueryController(final NetworkDataService networkDataService,
final SortMapper sortMapper,
- final QueryResponseFactory queryResponseFactory) {
+ final AnchorDetailsMapper anchorDetailsMapper,
+ @Value("${rest.api.base-path}") final String basePath) {
this.networkDataService = networkDataService;
this.sortMapper = sortMapper;
- this.queryResponseFactory = queryResponseFactory;
+ this.queryResponseFactory = new QueryResponseFactory(sortMapper, anchorDetailsMapper, basePath);
}
@Override
@@ -126,4 +138,148 @@ public class QueryController implements CpsTemporalQueryApi {
}
+ public static class QueryResponseFactory {
+
+ private SortMapper sortMapper;
+ private String basePath;
+ private AnchorDetailsMapper anchorDetailsMapper;
+
+ /**
+ * Constructor.
+ *
+ * @param sortMapper sortMapper
+ * @param anchorDetailsMapper anchorDetailsMapper
+ * @param basePath basePath
+ */
+ public QueryResponseFactory(final SortMapper sortMapper,
+ final AnchorDetailsMapper anchorDetailsMapper,
+ final String basePath) {
+ this.sortMapper = sortMapper;
+ this.anchorDetailsMapper = anchorDetailsMapper;
+ this.basePath = basePath;
+ }
+
+ /**
+ * Use search criteria and search result-set to create response.
+ *
+ * @param searchCriteria searchCriteria
+ * @param searchResult searchResult
+ * @return AnchorHistory
+ */
+ public AnchorHistory createAnchorsDataByFilterResponse(final SearchCriteria searchCriteria,
+ final Slice<NetworkData> searchResult) {
+
+ final var anchorHistory = new AnchorHistory();
+ if (searchResult.hasNext()) {
+ anchorHistory.setNextRecordsLink(
+ toRelativeLink(
+ getAbsoluteLinkForGetAnchorsDataByFilter(searchCriteria, searchResult.nextPageable())));
+ }
+ if (searchResult.hasPrevious()) {
+ anchorHistory.setPreviousRecordsLink(
+ toRelativeLink(
+ getAbsoluteLinkForGetAnchorsDataByFilter(searchCriteria, searchResult.previousPageable())));
+ }
+ anchorHistory.setRecords(convertToAnchorDetails(searchResult.getContent()));
+ return anchorHistory;
+ }
+
+ /**
+ * Use search criteria and search result-set to create response.
+ *
+ * @param searchCriteria searchCriteria
+ * @param searchResult searchResult
+ * @return AnchorHistory
+ */
+ public AnchorHistory createAnchorDataByNameResponse(final SearchCriteria searchCriteria,
+ final Slice<NetworkData> searchResult) {
+
+ final var anchorHistory = new AnchorHistory();
+ if (searchResult.hasNext()) {
+ anchorHistory.setNextRecordsLink(toRelativeLink(
+ getAbsoluteLinkForGetAnchorDataByName(searchCriteria, searchResult.nextPageable())));
+ }
+ if (searchResult.hasPrevious()) {
+ anchorHistory.setPreviousRecordsLink(toRelativeLink(
+ getAbsoluteLinkForGetAnchorDataByName(searchCriteria, searchResult.previousPageable())));
+ }
+ anchorHistory.setRecords(convertToAnchorDetails(searchResult.getContent()));
+ return anchorHistory;
+ }
+
+ private List<AnchorDetails> convertToAnchorDetails(final List<NetworkData> networkDataList) {
+ return networkDataList.stream()
+ .map(networkData -> anchorDetailsMapper.toAnchorDetails(networkData))
+ .collect(Collectors.toList());
+ }
+
+ /*
+ Spring hateoas only provides absolute link. But in the microservices, relative links will be more appropriate
+ */
+ private String toRelativeLink(final String absoluteLink) {
+
+ /* Spring hateoas Issue:
+ It does replace the variable defined at the Controller level,
+ so we are removing the variable name and replace it with basePath.
+ https://github.com/spring-projects/spring-hateoas/issues/361
+ https://github.com/spring-projects/spring-hateoas/pull/1375
+ */
+ final int contextPathBeginIndex = absoluteLink.indexOf("rest.api.base-path%257D");
+ return basePath + absoluteLink.substring(contextPathBeginIndex + 23);
+ }
+
+ private String getAbsoluteLinkForGetAnchorDataByName(final SearchCriteria searchCriteria,
+ final Pageable pageable) {
+ final var uriComponentsBuilder = linkTo(methodOn(QueryController.class).getAnchorDataByName(
+ searchCriteria.getDataspaceName(),
+ searchCriteria.getAnchorName(),
+ DateTimeUtility.toString(searchCriteria.getObservedAfter()),
+ null,
+ DateTimeUtility.toString(searchCriteria.getCreatedBefore()),
+ pageable.getPageNumber(), pageable.getPageSize(),
+ sortMapper.sortAsString(searchCriteria.getPageable().getSort())))
+ .toUriComponentsBuilder();
+ addSimplePayloadFilter(uriComponentsBuilder, searchCriteria.getSimplePayloadFilter());
+ return encodePlusSign(uriComponentsBuilder.toUriString());
+ }
+
+ private String getAbsoluteLinkForGetAnchorsDataByFilter(final SearchCriteria searchCriteria,
+ final Pageable pageable) {
+ final var uriComponentsBuilder = linkTo(methodOn(QueryController.class).getAnchorsDataByFilter(
+ searchCriteria.getDataspaceName(),
+ searchCriteria.getSchemaSetName(),
+ DateTimeUtility.toString(searchCriteria.getObservedAfter()),
+ null,
+ DateTimeUtility.toString(searchCriteria.getCreatedBefore()),
+ pageable.getPageNumber(), pageable.getPageSize(),
+ sortMapper.sortAsString(searchCriteria.getPageable().getSort())))
+ .toUriComponentsBuilder();
+ addSimplePayloadFilter(uriComponentsBuilder, searchCriteria.getSimplePayloadFilter());
+ return encodePlusSign(uriComponentsBuilder.toUriString());
+ }
+
+ /*
+ Spring hateoas does double encoding when generting URI.
+ To avoid it in the case of simplePayloadFilter,
+ the 'simplePayloadFilter is being added explicitly to UriComponentsBuilder
+ */
+ private UriComponentsBuilder addSimplePayloadFilter(final UriComponentsBuilder uriComponentsBuilder,
+ final String simplePayloadFilter) {
+ if (simplePayloadFilter != null) {
+ uriComponentsBuilder.queryParam("simplePayloadFilter", simplePayloadFilter);
+ }
+ return uriComponentsBuilder;
+ }
+
+ /*
+ Spring hateoas does not encode '+' in the query param but it deccodes '+' as space.
+ Due to this inconsistency, API was failing to convert datetime with positive timezone.
+ The fix is done in the spring-hateoas 1.4 version but it is yet to release.
+ As a workaround, we are replacing all the '+' with '%2B'
+ https://github.com/spring-projects/spring-hateoas/issues/1485
+ */
+ private String encodePlusSign(final String link) {
+ return link.replace("+", "%2B");
+ }
+ }
}
diff --git a/src/main/java/org/onap/cps/temporal/controller/rest/QueryResponseFactory.java b/src/main/java/org/onap/cps/temporal/controller/rest/QueryResponseFactory.java
deleted file mode 100644
index 6ac4759..0000000
--- a/src/main/java/org/onap/cps/temporal/controller/rest/QueryResponseFactory.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * ============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.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.temporal.controller.rest;
-
-import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
-import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
-
-import java.util.List;
-import java.util.stream.Collectors;
-import org.onap.cps.temporal.controller.rest.model.AnchorDetails;
-import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapper;
-import org.onap.cps.temporal.controller.rest.model.AnchorHistory;
-import org.onap.cps.temporal.controller.rest.model.SortMapper;
-import org.onap.cps.temporal.controller.utils.DateTimeUtility;
-import org.onap.cps.temporal.domain.NetworkData;
-import org.onap.cps.temporal.domain.SearchCriteria;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.domain.Slice;
-import org.springframework.stereotype.Component;
-import org.springframework.web.util.UriComponentsBuilder;
-
-@Component
-public class QueryResponseFactory {
-
- private SortMapper sortMapper;
- private String basePath;
- private AnchorDetailsMapper anchorDetailsMapper;
-
- /**
- * Constructor.
- *
- * @param sortMapper sortMapper
- * @param anchorDetailsMapper anchorDetailsMapper
- * @param basePath basePath
- */
- public QueryResponseFactory(final SortMapper sortMapper,
- final AnchorDetailsMapper anchorDetailsMapper,
- @Value("${rest.api.base-path}") final String basePath) {
- this.sortMapper = sortMapper;
- this.anchorDetailsMapper = anchorDetailsMapper;
- this.basePath = basePath;
- }
-
- AnchorHistory createAnchorsDataByFilterResponse(final SearchCriteria searchCriteria,
- final Slice<NetworkData> response) {
-
- final var anchorHistory = new AnchorHistory();
- if (response.hasNext()) {
- anchorHistory.setNextRecordsLink(
- toRelativeLink(getAbsoluteLinkForGetAnchorsDataByFilter(searchCriteria, response.nextPageable())));
- }
- if (response.hasPrevious()) {
- anchorHistory.setPreviousRecordsLink(
- toRelativeLink(
- getAbsoluteLinkForGetAnchorsDataByFilter(searchCriteria, response.previousPageable())));
- }
- anchorHistory.setRecords(convertToAnchorDetails(response.getContent()));
- return anchorHistory;
- }
-
- AnchorHistory createAnchorDataByNameResponse(final SearchCriteria searchCriteria,
- final Slice<NetworkData> response) {
-
- final var anchorHistory = new AnchorHistory();
- if (response.hasNext()) {
- anchorHistory.setNextRecordsLink(toRelativeLink(
- getAbsoluteLinkForGetAnchorDataByName(searchCriteria, response.nextPageable())));
- }
- if (response.hasPrevious()) {
- anchorHistory.setPreviousRecordsLink(toRelativeLink(
- getAbsoluteLinkForGetAnchorDataByName(searchCriteria, response.previousPageable())));
- }
- anchorHistory.setRecords(convertToAnchorDetails(response.getContent()));
- return anchorHistory;
- }
-
- private List<AnchorDetails> convertToAnchorDetails(final List<NetworkData> networkDataList) {
- return networkDataList.stream()
- .map(networkData -> anchorDetailsMapper.toAnchorDetails(networkData))
- .collect(Collectors.toList());
- }
-
- /*
- Spring hateoas only provides absolute link. But in the microservices, relative links will be more appropriate
- */
- private String toRelativeLink(final String absoluteLink) {
-
- /* Spring hateoas Issue:
- It does replace the variable defined at the Controller level,
- so we are removing the variable name and replace it with basePath.
- https://github.com/spring-projects/spring-hateoas/issues/361
- https://github.com/spring-projects/spring-hateoas/pull/1375
- */
- final int contextPathBeginIndex = absoluteLink.indexOf("rest.api.base-path%257D");
- return basePath + absoluteLink.substring(contextPathBeginIndex + 23);
- }
-
- private String getAbsoluteLinkForGetAnchorDataByName(final SearchCriteria searchCriteria,
- final Pageable pageable) {
- final var uriComponentsBuilder = linkTo(methodOn(QueryController.class).getAnchorDataByName(
- searchCriteria.getDataspaceName(),
- searchCriteria.getAnchorName(),
- DateTimeUtility.toString(searchCriteria.getObservedAfter()),
- null,
- DateTimeUtility.toString(searchCriteria.getCreatedBefore()),
- pageable.getPageNumber(), pageable.getPageSize(),
- sortMapper.sortAsString(searchCriteria.getPageable().getSort())))
- .toUriComponentsBuilder();
- addSimplePayloadFilter(uriComponentsBuilder, searchCriteria.getSimplePayloadFilter());
- return encodePlusSign(uriComponentsBuilder.toUriString());
- }
-
- private String getAbsoluteLinkForGetAnchorsDataByFilter(final SearchCriteria searchCriteria,
- final Pageable pageable) {
- final var uriComponentsBuilder = linkTo(methodOn(QueryController.class).getAnchorsDataByFilter(
- searchCriteria.getDataspaceName(),
- searchCriteria.getSchemaSetName(),
- DateTimeUtility.toString(searchCriteria.getObservedAfter()),
- null,
- DateTimeUtility.toString(searchCriteria.getCreatedBefore()),
- pageable.getPageNumber(), pageable.getPageSize(),
- sortMapper.sortAsString(searchCriteria.getPageable().getSort())))
- .toUriComponentsBuilder();
- addSimplePayloadFilter(uriComponentsBuilder, searchCriteria.getSimplePayloadFilter());
- return encodePlusSign(uriComponentsBuilder.toUriString());
- }
-
- /*
- Spring hateoas does double encoding when generting URI.
- To avoid it in the case of simplePayloadFilter,
- the 'simplePayloadFilter is being added explicitly to UriComponentsBuilder
- */
- private UriComponentsBuilder addSimplePayloadFilter(final UriComponentsBuilder uriComponentsBuilder,
- final String simplePayloadFilter) {
- if (simplePayloadFilter != null) {
- uriComponentsBuilder.queryParam("simplePayloadFilter", simplePayloadFilter);
- }
- return uriComponentsBuilder;
- }
-
- /*
- Spring hateoas does not encode '+' in the query param but it deccodes '+' as space.
- Due to this inconsistency, API was failing to convert datetime with positive timezone.
- The fix is done in the spring-hateoas 1.4 version but it is yet to release.
- As a workaround, we are replacing all the '+' with '%2B'
- https://github.com/spring-projects/spring-hateoas/issues/1485
- */
- private String encodePlusSign(final String link) {
- return link.replace("+", "%2B");
- }
-}
diff --git a/src/main/java/org/onap/cps/temporal/controller/rest/config/WebSecurityConfig.java b/src/main/java/org/onap/cps/temporal/controller/rest/config/WebSecurityConfig.java
new file mode 100644
index 0000000..647a0b0
--- /dev/null
+++ b/src/main/java/org/onap/cps/temporal/controller/rest/config/WebSecurityConfig.java
@@ -0,0 +1,80 @@
+/*
+ * ============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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.temporal.controller.rest.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+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.WebSecurityConfigurerAdapter;
+
+/**
+ * Configuration class to implement application security. It enforces Basic Authentication access control.
+ */
+@Configuration
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ private static final String USER_ROLE = "USER";
+
+ private final String username;
+ private final String password;
+ private final String[] permitUris;
+
+ /**
+ * Constructor. Accepts parameters from configuration.
+ *
+ * @param permitUris comma-separated list of uri patterns for endpoints permitted
+ * @param username username
+ * @param password password
+ */
+ public WebSecurityConfig(
+ @Autowired @Value("${security.permit-uri}") final String permitUris,
+ @Autowired @Value("${security.auth.username}") final String username,
+ @Autowired @Value("${security.auth.password}") final String password
+ ) {
+ super();
+ this.permitUris =
+ permitUris.isEmpty() ? new String[]{"/swagger/openapi.yml"} : permitUris.split("\\s{0,9},\\s{0,9}");
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ // The team decided to disable default CSRF Spring protection and not implement CSRF tokens validation.
+ // CPS is a stateless REST API that is not as vulnerable to CSRF attacks as web applications running in
+ // web browsers are. CPS does not manage sessions, each request requires the authentication token in the header.
+ // See https://docs.spring.io/spring-security/site/docs/5.3.8.RELEASE/reference/html5/#csrf
+ @SuppressWarnings("squid:S4502")
+ protected void configure(final HttpSecurity http) throws Exception {
+ http
+ .csrf().disable()
+ .authorizeRequests()
+ .antMatchers(permitUris).permitAll()
+ .anyRequest().authenticated()
+ .and().httpBasic();
+ }
+
+ @Override
+ protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication().withUser(username).password("{noop}" + password).roles(USER_ROLE);
+ }
+}
diff --git a/src/main/java/org/onap/cps/temporal/controller/rest/model/SortMapper.java b/src/main/java/org/onap/cps/temporal/controller/rest/model/SortMapper.java
index cd553eb..789284e 100644
--- a/src/main/java/org/onap/cps/temporal/controller/rest/model/SortMapper.java
+++ b/src/main/java/org/onap/cps/temporal/controller/rest/model/SortMapper.java
@@ -65,7 +65,7 @@ public class SortMapper {
for (final String eachSortAsString : sortingOrderAsString) {
final String[] eachSortDetail = eachSortAsString.split(FIELD_DIRECTION_SEPARATOR);
final var direction = Direction.fromString(eachSortDetail[1]);
- final var fieldName = eachSortDetail[0];
+ final String fieldName = eachSortDetail[0];
sortOrder.add(new Order(direction, fieldName));
}
return Sort.by(sortOrder);
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 41eddf8..a3b1cd8 100755
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -68,6 +68,12 @@ springdoc:
urls:
- name: query
url: /swagger/openapi.yml
+security:
+ # comma-separated uri patterns which do not require authorization
+ permit-uri: /manage/**,/swagger-ui/**,/swagger-resources/**,/swagger/openapi.yml
+ auth:
+ username: ${APP_USERNAME}
+ password: ${APP_PASSWORD}
# Actuator
management:
diff --git a/src/test/groovy/org/onap/cps/temporal/controller/rest/ControllerSecuritySpec.groovy b/src/test/groovy/org/onap/cps/temporal/controller/rest/ControllerSecuritySpec.groovy
new file mode 100644
index 0000000..2ced672
--- /dev/null
+++ b/src/test/groovy/org/onap/cps/temporal/controller/rest/ControllerSecuritySpec.groovy
@@ -0,0 +1,99 @@
+/*
+ * ============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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.temporal.controller.rest
+
+import org.onap.cps.temporal.controller.rest.config.WebSecurityConfig
+import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapper
+import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapperImpl
+import org.onap.cps.temporal.controller.rest.model.AnchorHistory
+import org.onap.cps.temporal.controller.rest.model.SortMapper
+import org.onap.cps.temporal.domain.NetworkData
+import org.onap.cps.temporal.service.NetworkDataService
+import org.spockframework.spring.SpringBean
+import org.spockframework.spring.StubBeans
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.context.annotation.Import
+import org.springframework.data.domain.Pageable
+import org.springframework.data.domain.Slice
+import org.springframework.data.domain.SliceImpl
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpStatus
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.setup.MockMvcBuilders
+import org.springframework.web.context.WebApplicationContext
+import spock.lang.Shared
+import spock.lang.Specification
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+
+@WebMvcTest(QueryController)
+@Import([WebSecurityConfig, SortMapper, AnchorDetailsMapperImpl])
+class ControllerSecuritySpec extends Specification {
+
+ @SpringBean
+ NetworkDataService mockNetworkDataService = Mock() {
+ searchNetworkData(_) >> new SliceImpl<NetworkData>([], Pageable.ofSize(1), false)
+ }
+
+ QueryController.QueryResponseFactory mockQueryResponseFactory = Mock()
+
+ MockMvc mvc
+
+ @Autowired
+ WebApplicationContext context
+
+ @Shared
+ def testEndpoint = '/cps-temporal/api/v1/dataspaces/my-dataspace/anchors/my-anchor/history'
+
+ def setup() {
+ mvc = MockMvcBuilders.webAppContextSetup(this.context).apply(springSecurity()).build();
+ }
+
+ def 'Get request with authentication: #scenario.'() {
+ given: 'authentication'
+ HttpHeaders httpHeaders = new HttpHeaders()
+ httpHeaders.setBasicAuth(username, password)
+ when: 'request is sent with authentication'
+ def response = mvc.perform(get(testEndpoint).headers(httpHeaders)
+ ).andReturn().response
+ then: 'expected http status is returned'
+ assert response.status == expectedHttpStatus.value()
+ where:
+ scenario | username | password || expectedHttpStatus
+ 'correct credentials' | 'testUser' | 'testPassword' || HttpStatus.OK
+ 'unknown username' | 'unknown-user' | 'password' || HttpStatus.UNAUTHORIZED
+ 'wrong password' | 'cpsuser' | 'wrong-password' || HttpStatus.UNAUTHORIZED
+ }
+
+ def 'Get urls without authentication : #scenario.'() {
+ when: 'request is sent without authentication'
+ def response = mvc.perform(get(url)
+ ).andReturn().response
+ then: 'expected http status is returned'
+ assert response.status == expectedHttpStatus.value()
+ where:
+ scenario | url | expectedHttpStatus
+ 'permitted url' | '/swagger/openapi.yml' | HttpStatus.OK
+ 'not-permitted url' | testEndpoint | HttpStatus.UNAUTHORIZED
+ }
+
+}
diff --git a/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy b/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy
index a18a134..7847b34 100644
--- a/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy
+++ b/src/test/groovy/org/onap/cps/temporal/controller/rest/QueryControllerSpec.groovy
@@ -21,8 +21,7 @@
package org.onap.cps.temporal.controller.rest
import org.onap.cps.temporal.controller.utils.DateTimeUtility
-import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
-
+import com.fasterxml.jackson.databind.ObjectMapper
import java.time.OffsetDateTime
import org.onap.cps.temporal.controller.rest.model.AnchorDetails
import org.onap.cps.temporal.controller.rest.model.AnchorDetailsMapperImpl
@@ -41,13 +40,14 @@ import org.springframework.data.domain.SliceImpl
import org.springframework.data.domain.Sort
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
+import org.springframework.security.test.context.support.WithMockUser
import org.springframework.test.web.servlet.MockMvc
-import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper
-import spock.lang.Shared
import spock.lang.Specification
+import spock.lang.Shared
@WebMvcTest(QueryController)
-@Import([SortMapper, QueryResponseFactory, AnchorDetailsMapperImpl])
+@Import([SortMapper, AnchorDetailsMapperImpl])
+@WithMockUser
class QueryControllerSpec extends Specification {
@SpringBean
diff --git a/src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy b/src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy
index 3d6a354..32bc660 100644
--- a/src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy
+++ b/src/test/groovy/org/onap/cps/temporal/domain/SearchCriteriaSpec.groovy
@@ -131,12 +131,12 @@ class SearchCriteriaSpec extends Specification {
then: 'exception is thrown'
def illegalArgumentException = thrown(IllegalArgumentException)
def message = illegalArgumentException.getMessage();
- assert message.contains("sort")
+ assert message.contains('sort')
assert message.contains(expectedExceptionMessage)
where:
scenario | sort | expectedExceptionMessage
- 'null' | null | "null"
- 'unsupported properties' | Sort.by(Sort.Direction.ASC, 'unsupported') | "Invalid sorting"
+ 'null' | null | 'null'
+ 'unsupported properties' | Sort.by(Sort.Direction.ASC, 'unsupported') | 'Invalid sorting'
'missing required sort' | Sort.by(Sort.Direction.ASC, 'anchor') | 'Missing mandatory sort'
}
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
index fce4a17..6765057 100644
--- a/src/test/resources/application.yml
+++ b/src/test/resources/application.yml
@@ -63,4 +63,11 @@ app:
topic: cps.cfg-state-events
query:
response:
- max-page-size: 20 \ No newline at end of file
+ max-page-size: 20
+
+security:
+ # comma-separated uri patterns which do not require authorization
+ permit-uri: /manage/**,/swagger-ui/**,/swagger-resources/**,/swagger/openapi.yml
+ auth:
+ username: testUser
+ password: testPassword