From 717018f3d1d8ef2a6f9b7e45be57a75eb7ea7050 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Tue, 7 May 2024 13:42:39 +0200 Subject: Adjust history package name to use org.onap.portalng instead of org.onap.portal Issue-ID: PORTALNG-92 Change-Id: I67f5a75834960ace8cb5ee6846c6741dc69fde2f Signed-off-by: Fiete Ostkamp --- app/build.gradle | 4 +- .../onap/portal/history/HistoryApplication.java | 40 -- .../portal/history/configuration/BeansConfig.java | 50 -- .../portal/history/configuration/Errorhandler.java | 94 ---- .../history/configuration/HistoryConfig.java | 35 -- .../configuration/RequestIdInterceptor.java | 56 -- .../history/configuration/SchedulerConfig.java | 52 -- .../history/configuration/SecurityConfig.java | 53 -- .../history/controller/ActionsController.java | 84 --- .../onap/portal/history/entities/ActionsDao.java | 44 -- .../portal/history/exception/ProblemException.java | 55 -- .../portal/history/logging/LogContextVariable.java | 40 -- .../portal/history/logging/LoggerProperties.java | 11 - .../onap/portal/history/logging/LoggingHelper.java | 68 --- .../logging/ReactiveRequestLoggingFilter.java | 89 ---- .../onap/portal/history/logging/StatusCode.java | 28 - .../portal/history/logging/WebExchangeUtils.java | 92 ---- .../history/repository/ActionsRepository.java | 42 -- .../portal/history/services/ActionsService.java | 218 -------- .../onap/portal/history/util/IdTokenExchange.java | 126 ----- .../java/org/onap/portal/history/util/Logger.java | 65 --- .../onap/portalng/history/HistoryApplication.java | 40 ++ .../history/configuration/BeansConfig.java | 50 ++ .../history/configuration/Errorhandler.java | 95 ++++ .../history/configuration/HistoryConfig.java | 35 ++ .../configuration/RequestIdInterceptor.java | 56 ++ .../history/configuration/SchedulerConfig.java | 53 ++ .../history/configuration/SecurityConfig.java | 53 ++ .../history/controller/ActionsController.java | 84 +++ .../onap/portalng/history/entities/ActionsDao.java | 44 ++ .../history/exception/ProblemException.java | 55 ++ .../history/logging/LogContextVariable.java | 40 ++ .../portalng/history/logging/LoggerProperties.java | 11 + .../portalng/history/logging/LoggingHelper.java | 68 +++ .../logging/ReactiveRequestLoggingFilter.java | 89 ++++ .../onap/portalng/history/logging/StatusCode.java | 28 + .../portalng/history/logging/WebExchangeUtils.java | 92 ++++ .../history/repository/ActionsRepository.java | 42 ++ .../portalng/history/services/ActionsService.java | 218 ++++++++ .../portalng/history/util/IdTokenExchange.java | 126 +++++ .../org/onap/portalng/history/util/Logger.java | 65 +++ .../onap/portal/history/BaseIntegrationTest.java | 180 ------- .../org/onap/portal/history/TokenGenerator.java | 129 ----- .../org/onap/portal/history/actions/ActionDto.java | 39 -- .../portal/history/actions/ActionFixtures.java | 126 ----- .../actions/ActionsControllerIntegrationTest.java | 571 --------------------- .../onap/portalng/history/BaseIntegrationTest.java | 181 +++++++ .../org/onap/portalng/history/TokenGenerator.java | 129 +++++ .../onap/portalng/history/actions/ActionDto.java | 39 ++ .../portalng/history/actions/ActionFixtures.java | 126 +++++ .../actions/ActionsControllerIntegrationTest.java | 571 +++++++++++++++++++++ ...tal.history.java-application-conventions.gradle | 11 - ...p.portal.history.java-common-conventions.gradle | 28 - ....portal.history.java-library-conventions.gradle | 11 - ...lng.history.java-application-conventions.gradle | 11 + ...portalng.history.java-common-conventions.gradle | 28 + ...ortalng.history.java-library-conventions.gradle | 11 + openapi/build.gradle | 12 +- 58 files changed, 2448 insertions(+), 2445 deletions(-) delete mode 100644 app/src/main/java/org/onap/portal/history/HistoryApplication.java delete mode 100644 app/src/main/java/org/onap/portal/history/configuration/BeansConfig.java delete mode 100644 app/src/main/java/org/onap/portal/history/configuration/Errorhandler.java delete mode 100644 app/src/main/java/org/onap/portal/history/configuration/HistoryConfig.java delete mode 100644 app/src/main/java/org/onap/portal/history/configuration/RequestIdInterceptor.java delete mode 100644 app/src/main/java/org/onap/portal/history/configuration/SchedulerConfig.java delete mode 100644 app/src/main/java/org/onap/portal/history/configuration/SecurityConfig.java delete mode 100644 app/src/main/java/org/onap/portal/history/controller/ActionsController.java delete mode 100644 app/src/main/java/org/onap/portal/history/entities/ActionsDao.java delete mode 100644 app/src/main/java/org/onap/portal/history/exception/ProblemException.java delete mode 100644 app/src/main/java/org/onap/portal/history/logging/LogContextVariable.java delete mode 100644 app/src/main/java/org/onap/portal/history/logging/LoggerProperties.java delete mode 100644 app/src/main/java/org/onap/portal/history/logging/LoggingHelper.java delete mode 100644 app/src/main/java/org/onap/portal/history/logging/ReactiveRequestLoggingFilter.java delete mode 100644 app/src/main/java/org/onap/portal/history/logging/StatusCode.java delete mode 100644 app/src/main/java/org/onap/portal/history/logging/WebExchangeUtils.java delete mode 100644 app/src/main/java/org/onap/portal/history/repository/ActionsRepository.java delete mode 100644 app/src/main/java/org/onap/portal/history/services/ActionsService.java delete mode 100644 app/src/main/java/org/onap/portal/history/util/IdTokenExchange.java delete mode 100644 app/src/main/java/org/onap/portal/history/util/Logger.java create mode 100644 app/src/main/java/org/onap/portalng/history/HistoryApplication.java create mode 100644 app/src/main/java/org/onap/portalng/history/configuration/BeansConfig.java create mode 100644 app/src/main/java/org/onap/portalng/history/configuration/Errorhandler.java create mode 100644 app/src/main/java/org/onap/portalng/history/configuration/HistoryConfig.java create mode 100644 app/src/main/java/org/onap/portalng/history/configuration/RequestIdInterceptor.java create mode 100644 app/src/main/java/org/onap/portalng/history/configuration/SchedulerConfig.java create mode 100644 app/src/main/java/org/onap/portalng/history/configuration/SecurityConfig.java create mode 100644 app/src/main/java/org/onap/portalng/history/controller/ActionsController.java create mode 100644 app/src/main/java/org/onap/portalng/history/entities/ActionsDao.java create mode 100644 app/src/main/java/org/onap/portalng/history/exception/ProblemException.java create mode 100644 app/src/main/java/org/onap/portalng/history/logging/LogContextVariable.java create mode 100644 app/src/main/java/org/onap/portalng/history/logging/LoggerProperties.java create mode 100644 app/src/main/java/org/onap/portalng/history/logging/LoggingHelper.java create mode 100644 app/src/main/java/org/onap/portalng/history/logging/ReactiveRequestLoggingFilter.java create mode 100644 app/src/main/java/org/onap/portalng/history/logging/StatusCode.java create mode 100644 app/src/main/java/org/onap/portalng/history/logging/WebExchangeUtils.java create mode 100644 app/src/main/java/org/onap/portalng/history/repository/ActionsRepository.java create mode 100644 app/src/main/java/org/onap/portalng/history/services/ActionsService.java create mode 100644 app/src/main/java/org/onap/portalng/history/util/IdTokenExchange.java create mode 100644 app/src/main/java/org/onap/portalng/history/util/Logger.java delete mode 100644 app/src/test/java/org/onap/portal/history/BaseIntegrationTest.java delete mode 100644 app/src/test/java/org/onap/portal/history/TokenGenerator.java delete mode 100644 app/src/test/java/org/onap/portal/history/actions/ActionDto.java delete mode 100644 app/src/test/java/org/onap/portal/history/actions/ActionFixtures.java delete mode 100644 app/src/test/java/org/onap/portal/history/actions/ActionsControllerIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portalng/history/BaseIntegrationTest.java create mode 100644 app/src/test/java/org/onap/portalng/history/TokenGenerator.java create mode 100644 app/src/test/java/org/onap/portalng/history/actions/ActionDto.java create mode 100644 app/src/test/java/org/onap/portalng/history/actions/ActionFixtures.java create mode 100644 app/src/test/java/org/onap/portalng/history/actions/ActionsControllerIntegrationTest.java delete mode 100644 buildSrc/src/main/groovy/org.onap.portal.history.java-application-conventions.gradle delete mode 100644 buildSrc/src/main/groovy/org.onap.portal.history.java-common-conventions.gradle delete mode 100644 buildSrc/src/main/groovy/org.onap.portal.history.java-library-conventions.gradle create mode 100644 buildSrc/src/main/groovy/org.onap.portalng.history.java-application-conventions.gradle create mode 100644 buildSrc/src/main/groovy/org.onap.portalng.history.java-common-conventions.gradle create mode 100644 buildSrc/src/main/groovy/org.onap.portalng.history.java-library-conventions.gradle diff --git a/app/build.gradle b/app/build.gradle index 54f80b2..2f2dfae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ * This file was generated by the Gradle 'init' task. */ plugins { - id 'org.onap.portal.history.java-application-conventions' + id 'org.onap.portalng.history.java-application-conventions' id 'io.spring.dependency-management' id 'org.springframework.boot' id 'jacoco' @@ -78,7 +78,7 @@ jacocoTestReport { application { // Define the main class for the application. - mainClass = 'org.onap.portal.history.HistoryApplication' + mainClass = 'org.onap.portalng.history.HistoryApplication' } sourceCompatibility = '17' diff --git a/app/src/main/java/org/onap/portal/history/HistoryApplication.java b/app/src/main/java/org/onap/portal/history/HistoryApplication.java deleted file mode 100644 index 307d883..0000000 --- a/app/src/main/java/org/onap/portal/history/HistoryApplication.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -/* - * This Java source file was generated by the Gradle 'init' task. - */ -package org.onap.portal.history; - - -import org.onap.portal.history.configuration.HistoryConfig; -import org.onap.portal.history.logging.LoggerProperties; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; - -@SpringBootApplication -@EnableConfigurationProperties({HistoryConfig.class, LoggerProperties.class}) -public class HistoryApplication { - public static void main(String[] args) { - SpringApplication.run(HistoryApplication.class, args); - } -} diff --git a/app/src/main/java/org/onap/portal/history/configuration/BeansConfig.java b/app/src/main/java/org/onap/portal/history/configuration/BeansConfig.java deleted file mode 100644 index 9a60681..0000000 --- a/app/src/main/java/org/onap/portal/history/configuration/BeansConfig.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.configuration; - - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import org.zalando.problem.jackson.ProblemModule; - -import java.time.Clock; - -@Configuration -public class BeansConfig { - @Bean - Clock clock() { - return Clock.systemUTC(); - } - - @Bean - public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { - return builder - .modules(new ProblemModule(), new JavaTimeModule()) - .build() - .setSerializationInclusion(JsonInclude.Include.NON_NULL); - } - -} diff --git a/app/src/main/java/org/onap/portal/history/configuration/Errorhandler.java b/app/src/main/java/org/onap/portal/history/configuration/Errorhandler.java deleted file mode 100644 index 583420b..0000000 --- a/app/src/main/java/org/onap/portal/history/configuration/Errorhandler.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.configuration; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.onap.portal.history.exception.ProblemException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import reactor.core.publisher.Mono; - -@Component -public class Errorhandler implements ErrorWebExceptionHandler { - - @Autowired - ObjectMapper objectMapper; - - /** - * Override the handle methode to implement custom error handling - * Set response status code to BAD REQUEST, set header content-type and fill the body with the Problem object along the API model - */ - @Override - public Mono handle(ServerWebExchange exchange, Throwable ex) { - ServerHttpResponse httpResponse = exchange.getResponse(); - setResponseStatus(httpResponse, ex); - httpResponse.getHeaders().add("Content-Type", "application/problem+json"); - return httpResponse.writeWith(Mono.fromSupplier(() -> { - DataBufferFactory bufferFactory = httpResponse.bufferFactory(); - try { - return - (httpResponse.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) - ? httpResponse.bufferFactory().wrap(objectMapper.writeValueAsBytes(setProblemException(httpResponse, ex.getMessage()))) - : httpResponse.bufferFactory().wrap(objectMapper.writeValueAsBytes(ex)); - } catch (JsonProcessingException e) { - return bufferFactory.wrap(new byte[0]); - } - })); - } - - /** - * Set the response status - * @param httpResponse response which status code should be set - * @param ex throwable exception to identify the Problem class - */ - private void setResponseStatus(ServerHttpResponse httpResponse, Throwable ex) { - if (ex instanceof Problem) { - httpResponse.setStatusCode(HttpStatus.BAD_REQUEST); - } else { - httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - /** - * Build a problem exception and set the response status code to BAD REQUEST for every response - * @param httpResponse response which status code should be set - * @param message for the detail of the problem exception - * @return problem exception instance - */ - private ProblemException setProblemException(ServerHttpResponse httpResponse, String message){ - httpResponse.setStatusCode(HttpStatus.BAD_REQUEST); - return ProblemException.builder() - .status(Status.INTERNAL_SERVER_ERROR) - .title(Status.INTERNAL_SERVER_ERROR.getReasonPhrase()) - .detail(message) - .build(); - - } -} diff --git a/app/src/main/java/org/onap/portal/history/configuration/HistoryConfig.java b/app/src/main/java/org/onap/portal/history/configuration/HistoryConfig.java deleted file mode 100644 index f70a9d4..0000000 --- a/app/src/main/java/org/onap/portal/history/configuration/HistoryConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.configuration; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import jakarta.validation.constraints.NotBlank; - -@Data -@ConfigurationProperties("history") -public class HistoryConfig { - - @NotBlank - private final Integer saveInterval; -} diff --git a/app/src/main/java/org/onap/portal/history/configuration/RequestIdInterceptor.java b/app/src/main/java/org/onap/portal/history/configuration/RequestIdInterceptor.java deleted file mode 100644 index aa1fd4e..0000000 --- a/app/src/main/java/org/onap/portal/history/configuration/RequestIdInterceptor.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.configuration; - -import org.onap.portal.history.util.Logger; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.reactive.ServerWebExchangeContextFilter; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; - -import java.util.List; - -@Component -public class RequestIdInterceptor implements WebFilter { - public static final String EXCHANGE_CONTEXT_ATTRIBUTE = ServerWebExchangeContextFilter.class.getName() - + ".EXCHANGE_CONTEXT"; - - public static final String X_REQUEST_ID = "X-Request-Id"; - - /** - * Override a web filter to write log entries for every request and response and - * add header in response with X_REQUEST_ID - */ - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - List xRequestIdList = exchange.getRequest().getHeaders().get(X_REQUEST_ID); - if (xRequestIdList != null && !xRequestIdList.isEmpty()) { - String xRequestId = xRequestIdList.get(0); - exchange.getResponse().getHeaders().add(X_REQUEST_ID, xRequestId); - } - return chain - .filter(exchange) - .contextWrite(cxt -> cxt.put(EXCHANGE_CONTEXT_ATTRIBUTE, exchange)); - } -} diff --git a/app/src/main/java/org/onap/portal/history/configuration/SchedulerConfig.java b/app/src/main/java/org/onap/portal/history/configuration/SchedulerConfig.java deleted file mode 100644 index 88531af..0000000 --- a/app/src/main/java/org/onap/portal/history/configuration/SchedulerConfig.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.configuration; - -import org.onap.portal.history.services.ActionsService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@EnableScheduling -public class SchedulerConfig { - - private final ActionsService actionsService; - private final HistoryConfig historyConfig; - - public SchedulerConfig(ActionsService actionsService, HistoryConfig historyConfig){ - this.actionsService = actionsService; - this.historyConfig = historyConfig; - } - - /** - * This method will be trigger by Spring Boot scheduler. - * The cron execution time is configured in the application properties as well as the save interval. - */ - @Scheduled(cron="${history.delete-interval}") - public void runDeleteActions(){ - actionsService.deleteActions(historyConfig.getSaveInterval()); - log.info("Delete actions in scheduled job"); - } -} diff --git a/app/src/main/java/org/onap/portal/history/configuration/SecurityConfig.java b/app/src/main/java/org/onap/portal/history/configuration/SecurityConfig.java deleted file mode 100644 index e825295..0000000 --- a/app/src/main/java/org/onap/portal/history/configuration/SecurityConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.configuration; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.web.server.SecurityWebFilterChain; - -/** - * Configures the access control of the API endpoints. - */ -// https://hantsy.github.io/spring-reactive-sample/security/config.html -@EnableWebFluxSecurity -@Configuration -public class SecurityConfig { - - @Bean - public SecurityWebFilterChain springSecurityWebFilterChain(ServerHttpSecurity http) { - return http.httpBasic().disable() - .formLogin().disable() - .csrf().disable() - .cors() - .and() - .authorizeExchange() - .pathMatchers(HttpMethod.GET, "/actuator/**").permitAll() - .anyExchange().authenticated() - .and() - .oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt) - .build(); - } -} diff --git a/app/src/main/java/org/onap/portal/history/controller/ActionsController.java b/app/src/main/java/org/onap/portal/history/controller/ActionsController.java deleted file mode 100644 index ce781f5..0000000 --- a/app/src/main/java/org/onap/portal/history/controller/ActionsController.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.controller; - -import java.util.Optional; - -import org.onap.portal.history.configuration.HistoryConfig; -import org.onap.portal.history.openapi.api.ActionsApi; -import org.onap.portal.history.openapi.model.ActionResponse; -import org.onap.portal.history.openapi.model.ActionsListResponse; -import org.onap.portal.history.openapi.model.CreateActionRequest; -import org.onap.portal.history.services.ActionsService; -import org.onap.portal.history.util.IdTokenExchange; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ServerWebExchange; - -import reactor.core.publisher.Mono; - -@RestController -public class ActionsController implements ActionsApi { - - private final ActionsService actionsService; - private final HistoryConfig historyConfig; - - public ActionsController(ActionsService actionsService, HistoryConfig historyConfig){ - this.actionsService = actionsService; - this.historyConfig = historyConfig; - } - - @Override - public Mono> createAction(String userId, String xRequestId, Mono createActionRequest, ServerWebExchange exchange) { - - return IdTokenExchange - .validateUserId(userId, exchange, xRequestId) - .then(createActionRequest.flatMap(action -> actionsService.createActions(userId, action, historyConfig.getSaveInterval(), xRequestId))) - .map(ResponseEntity::ok); - } - - @Override - public Mono> deleteActions(String userId, String xRequestId, Integer deleteAfterHours, ServerWebExchange exchange) { - - return IdTokenExchange - .validateUserId(userId, exchange, xRequestId) - .then(actionsService.deleteUserActions(userId, deleteAfterHours, xRequestId)) - .map(ResponseEntity::ok); - } - - @Override - public Mono> getActions(String userId, String xRequestId, Optional page, Optional pageSize, Optional showLastHours, ServerWebExchange exchange) { - - return IdTokenExchange - .validateUserId(userId, exchange, xRequestId) - .then(actionsService.getActions(userId, page.orElse(1), pageSize.orElse(10), showLastHours.orElse(historyConfig.getSaveInterval()), historyConfig.getSaveInterval(), xRequestId)) - .map(ResponseEntity::ok); - } - - @Override - public Mono> listActions(String xRequestId, Optional page, Optional pageSize, Optional showLastHours, ServerWebExchange exchange) { - - return actionsService - .listActions(page.orElse(1), pageSize.orElse(10), showLastHours.orElse(historyConfig.getSaveInterval()), historyConfig.getSaveInterval(), xRequestId) - .map(ResponseEntity::ok); - } -} diff --git a/app/src/main/java/org/onap/portal/history/entities/ActionsDao.java b/app/src/main/java/org/onap/portal/history/entities/ActionsDao.java deleted file mode 100644 index 5457e87..0000000 --- a/app/src/main/java/org/onap/portal/history/entities/ActionsDao.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.entities; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.data.mongodb.core.mapping.Document; -import java.util.Date; - -/** - * Data access object for the actions in the MongoDB repository. - * No database id is set in this class because MongoDB use internal _id as primary key / uniq object identifier - */ -@Document(collection = "actions") -@Getter -@Setter -public class ActionsDao { - - private String userId; - - private Date actionCreatedAt; - - private Object action; - -} diff --git a/app/src/main/java/org/onap/portal/history/exception/ProblemException.java b/app/src/main/java/org/onap/portal/history/exception/ProblemException.java deleted file mode 100644 index f51d246..0000000 --- a/app/src/main/java/org/onap/portal/history/exception/ProblemException.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.exception; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import org.zalando.problem.AbstractThrowableProblem; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.StatusType; - -import java.net.URI; - -/** - * Default problem exception. This class has the same structure as the problem response model from the api. - */ -@Data -@Builder -@AllArgsConstructor -@NoArgsConstructor -@EqualsAndHashCode(callSuper = true) -public class ProblemException extends AbstractThrowableProblem { - @Builder.Default private final URI type = Problem.DEFAULT_TYPE; - - @Builder.Default private final String title = "Bad history error"; - - @Builder.Default private final StatusType status = Status.BAD_REQUEST; - - @Builder.Default private final String detail = "Please add more details here"; - - @Builder.Default private final URI instance = null; - -} diff --git a/app/src/main/java/org/onap/portal/history/logging/LogContextVariable.java b/app/src/main/java/org/onap/portal/history/logging/LogContextVariable.java deleted file mode 100644 index 76cb29e..0000000 --- a/app/src/main/java/org/onap/portal/history/logging/LogContextVariable.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * - * Copyright (c) 2023. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.logging; - -import lombok.Getter; - -@Getter -public enum LogContextVariable { - TRACE_ID("trace_id"), - STATUS("status"), - NORTHBOUND_METHOD("northbound.method"), - NORTHBOUND_URL("northbound.url"), - EXECUTION_TIME("execution.time_ms"), - HTTP_STATUS("httpStatus"); - - private final String variableName; - - LogContextVariable(String variableName) { - this.variableName = variableName; - } -} diff --git a/app/src/main/java/org/onap/portal/history/logging/LoggerProperties.java b/app/src/main/java/org/onap/portal/history/logging/LoggerProperties.java deleted file mode 100644 index c142b4f..0000000 --- a/app/src/main/java/org/onap/portal/history/logging/LoggerProperties.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.onap.portal.history.logging; - -import java.util.List; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties("logger") -public record LoggerProperties( - String traceIdHeaderName, - Boolean enabled, List excludePaths) { -} \ No newline at end of file diff --git a/app/src/main/java/org/onap/portal/history/logging/LoggingHelper.java b/app/src/main/java/org/onap/portal/history/logging/LoggingHelper.java deleted file mode 100644 index e587959..0000000 --- a/app/src/main/java/org/onap/portal/history/logging/LoggingHelper.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * - * Copyright (c) 2023. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.logging; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.MDC; - -import java.util.Map; -import java.util.function.BiConsumer; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class LoggingHelper { - public static void error( - Logger logger, Map metadata, String message, Object... args) { - log(logger::error, metadata, message, args); - } - - public static void debug( - Logger logger, Map metadata, String message, Object... args) { - log(logger::debug, metadata, message, args); - } - - public static void info( - Logger logger, Map metadata, String message, Object... args) { - log(logger::info, metadata, message, args); - } - - public static void warn( - Logger logger, Map metadata, String message, Object... args) { - log(logger::warn, metadata, message, args); - } - - public static void trace( - Logger logger, Map metadata, String message, Object... args) { - log(logger::trace, metadata, message, args); - } - - private static void log( - BiConsumer logMethod, - Map metadata, - String message, - Object... args) { - metadata.forEach((variable, value) -> MDC.put(variable.getVariableName(), value)); - logMethod.accept(message, args); - MDC.clear(); - } -} diff --git a/app/src/main/java/org/onap/portal/history/logging/ReactiveRequestLoggingFilter.java b/app/src/main/java/org/onap/portal/history/logging/ReactiveRequestLoggingFilter.java deleted file mode 100644 index 059e573..0000000 --- a/app/src/main/java/org/onap/portal/history/logging/ReactiveRequestLoggingFilter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * - * Copyright (c) 2023. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.logging; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.List; - -@Slf4j -@Component -@RequiredArgsConstructor -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) -public class ReactiveRequestLoggingFilter implements WebFilter { - - private final LoggerProperties loggerProperties; - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - if (loggingDisabled(exchange)) { - return chain.filter(exchange); - } - - var logMessageMetadata = - WebExchangeUtils.getRequestMetadata(exchange, loggerProperties.traceIdHeaderName()); - - LoggingHelper.info(log, logMessageMetadata, "RECEIVED"); - - var invocationStart = LocalDateTime.now(); - return chain - .filter(exchange) - .doOnTerminate( - () -> { - logMessageMetadata.put( - LogContextVariable.STATUS, - exchange.getResponse().getStatusCode().isError() - ? StatusCode.ERROR.name() - : StatusCode.COMPLETE.name()); - logMessageMetadata.put( - LogContextVariable.HTTP_STATUS, - String.valueOf(exchange.getResponse().getStatusCode().value())); - logMessageMetadata.put( - LogContextVariable.EXECUTION_TIME, - String.valueOf( - Duration.between(invocationStart, LocalDateTime.now()).toMillis())); - }) - .doOnSuccess(res -> LoggingHelper.info(log, logMessageMetadata, "FINISHED")) - .doOnError( - ex -> LoggingHelper.warn(log, logMessageMetadata, "FAILED: {}", ex.getMessage())); - } - - private boolean loggingDisabled(ServerWebExchange exchange) { - boolean loggingDisabled = loggerProperties.enabled() == null || !loggerProperties.enabled(); - - boolean urlShouldBeSkipped = - WebExchangeUtils.matchUrlsPatternsToPath(loggerProperties.excludePaths(), exchange.getRequest().getPath().value()); - - return loggingDisabled || urlShouldBeSkipped; - } -} diff --git a/app/src/main/java/org/onap/portal/history/logging/StatusCode.java b/app/src/main/java/org/onap/portal/history/logging/StatusCode.java deleted file mode 100644 index 1c93bd9..0000000 --- a/app/src/main/java/org/onap/portal/history/logging/StatusCode.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * - * Copyright (c) 2023. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.logging; - -public enum StatusCode { - REQUEST, - COMPLETE, - ERROR -} diff --git a/app/src/main/java/org/onap/portal/history/logging/WebExchangeUtils.java b/app/src/main/java/org/onap/portal/history/logging/WebExchangeUtils.java deleted file mode 100644 index 8efda9c..0000000 --- a/app/src/main/java/org/onap/portal/history/logging/WebExchangeUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * - * Copyright (c) 2023. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.logging; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.PathMatcher; -import org.springframework.web.server.ServerWebExchange; - -import java.util.EnumMap; -import java.util.List; -import java.util.Map; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class WebExchangeUtils { - private static final String DEFAULT_TRACE_ID = "REQUEST_ID_IS_NOT_SET"; - private static final String DEFAULT_REQUEST_URL = "REQUEST_URL_IS_ABSENT"; - private static final String DEFAULT_REQUEST_METHOD = "HTTP_METHOD_IS_ABSENT"; - - private static final PathMatcher pathMatcher = new AntPathMatcher(); - - public static String getRequestId(ServerWebExchange webExchange, String traceIdHeaderName) { - if (webExchange == null || traceIdHeaderName == null) { - return DEFAULT_TRACE_ID; - } - - var requestIdHeaders = webExchange.getRequest().getHeaders().get(traceIdHeaderName); - if (requestIdHeaders != null) { - return requestIdHeaders.stream().findAny().orElse(DEFAULT_TRACE_ID); - } else { - return DEFAULT_TRACE_ID; - } - } - - public static String getRequestUrl(ServerWebExchange webExchange) { - if (webExchange == null) { - return DEFAULT_REQUEST_URL; - } - return webExchange.getRequest().getURI().toString(); - } - - public static String getRequestHttpMethod(ServerWebExchange webExchange) { - if (webExchange == null) { - return DEFAULT_REQUEST_METHOD; - } - return webExchange.getRequest().getMethod().name(); - } - - public static boolean matchUrlPatternToPath(String pattern, String path) { - return pathMatcher.match(pattern, path); - } - - public static boolean matchUrlsPatternsToPath(List patterns, String path) { - return patterns != null - && patterns.stream().anyMatch(pathPattern -> matchUrlPatternToPath(pathPattern, path)); - } - - public static Map getRequestMetadata( - ServerWebExchange exchange, String traceIdHeaderName) { - var traceId = WebExchangeUtils.getRequestId(exchange, traceIdHeaderName); - var requestMethod = WebExchangeUtils.getRequestHttpMethod(exchange); - var requestUrl = WebExchangeUtils.getRequestUrl(exchange); - - var logMessageMetadata = new EnumMap(LogContextVariable.class); - logMessageMetadata.put(LogContextVariable.TRACE_ID, traceId); - logMessageMetadata.put(LogContextVariable.STATUS, StatusCode.REQUEST.name()); - logMessageMetadata.put(LogContextVariable.NORTHBOUND_METHOD, requestMethod); - logMessageMetadata.put(LogContextVariable.NORTHBOUND_URL, requestUrl); - - return logMessageMetadata; - } -} diff --git a/app/src/main/java/org/onap/portal/history/repository/ActionsRepository.java b/app/src/main/java/org/onap/portal/history/repository/ActionsRepository.java deleted file mode 100644 index 79fc378..0000000 --- a/app/src/main/java/org/onap/portal/history/repository/ActionsRepository.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.repository; - -import java.util.Date; - -import org.onap.portal.history.entities.ActionsDao; -import org.springframework.data.domain.Pageable; -import org.springframework.data.mongodb.repository.ReactiveMongoRepository; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -public interface ActionsRepository extends ReactiveMongoRepository { - - Flux findAllByActionCreatedAtAfter(Pageable pageable, Date actionCreatedAt); - - Flux findAllByUserIdAndActionCreatedAtAfter(Pageable pageable, String userId, Date actionCreatedAt); - - Mono deleteAllByUserIdAndActionCreatedAtIsBefore(String userId, Date actionCreatedAt); - - Mono deleteAllByActionCreatedAtIsBefore(Date actionCreatedAt); -} diff --git a/app/src/main/java/org/onap/portal/history/services/ActionsService.java b/app/src/main/java/org/onap/portal/history/services/ActionsService.java deleted file mode 100644 index a14fef2..0000000 --- a/app/src/main/java/org/onap/portal/history/services/ActionsService.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.services; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Date; - -import org.onap.portal.history.entities.ActionsDao; -import org.onap.portal.history.exception.ProblemException; -import org.onap.portal.history.openapi.model.ActionResponse; -import org.onap.portal.history.openapi.model.ActionsListResponse; -import org.onap.portal.history.openapi.model.CreateActionRequest; -import org.onap.portal.history.repository.ActionsRepository; -import org.onap.portal.history.util.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; - -import lombok.extern.slf4j.Slf4j; -import reactor.core.publisher.Mono; - - -@Slf4j -@Service -public class ActionsService { - - @Autowired - private ActionsRepository repository; - - /** - * Retrieve actions for a given userId from the database and provide a list with actions - * @param userId only actions for this userId should be retrieved - * @param page which page should be retrieved from the list of actions. From a user perspective the first page has the page number 1. - * In the response list the first page starts with 0. Therefore, a subtraction is needed. - * @param pageSize length of the response list - * @param showLastHours for which hours from the current time the actions should be retrieved. - * @param saveInterval value will be part of the response action object. This value is set in the application properties. - * In the future this value can be provided from the client. - * @param xRequestId from the request header. Will be used in an error log - * @return If successful object with an item list of action objects and an item with the list count, otherwise Mono error - */ - public Mono getActions(String userId, Integer page, Integer pageSize, Integer showLastHours, Integer saveInterval, String xRequestId){ - Pageable paging = PageRequest.of(page - 1 , pageSize, Sort.by(Sort.Direction.DESC, "actionCreatedAt")); - var dateAfter = Date.from(ZonedDateTime.now().minusHours(showLastHours).toInstant()); - return repository - .findAllByUserIdAndActionCreatedAtAfter(paging,userId, dateAfter) - .map(actionDao -> toActionResponse(actionDao, saveInterval)) - .collectList() - .map(this::toActionsListResponse) - .switchIfEmpty(Mono.just(new ActionsListResponse().totalCount(0))) - .onErrorResume(ex -> { - Logger.errorLog(xRequestId,"Get actions cannot be executed for user with id ", userId); - return getError("Get actions can not be executed for user with id " + userId); - }); - } - - /** - * Create an action data record in the database - * @param userId the id of the user for which the action should be stored - * @param createActionRequest the action object which should be stored - * @param saveInterval value will be part of the response action object. This value is set in the application properties. - * In the future this value can be provided from the client. - * @param xRequestId from the request header. Will be used in an error log - * @return If successful object with the stored action, otherwise Mono error - */ - public Mono createActions(String userId, CreateActionRequest createActionRequest, Integer saveInterval, String xRequestId) { - return repository - .save(toActionsDao(userId, createActionRequest)) - .map(action -> toActionResponse(action, saveInterval)) - .onErrorResume(ex -> { - Logger.errorLog(xRequestId,"Action for user can not be executed for user with id ", userId ); - return Mono.error(ProblemException.builder() - .type(Problem.DEFAULT_TYPE) - .status(Status.BAD_REQUEST) - .title(HttpStatus.BAD_REQUEST.toString()) - .detail("Action for user can not be executed for user with id " + userId) - .build()); - }); - } - - /** - * List all actions without a userId filter. - * @param page which page should be retrieved from the list of actions. From a user perspective the first page has the page number 1. - * In the response list the first page starts with 0. Therefore, a subtraction is needed. - * @param pageSize length of the response list - * @param showLastHours for which hours from the current time the actions should be retrieved. - * @param saveInterval value will be part of the response action object. This value is set in the application properties. - * * In the future this value can be provided from the client. - * @param xRequestId from the request header. Will be used in an error log - * @return If successful list with action response object, otherwise Mono error - */ - public Mono listActions(Integer page, Integer pageSize, Integer showLastHours, Integer saveInterval, String xRequestId){ - - var paging = PageRequest.of(page - 1 , pageSize, Sort.by(Sort.Direction.DESC, "actionCreatedAt")); - var dateAfter = Date.from(ZonedDateTime.now().minusHours(showLastHours).toInstant()); - - return repository - .findAllByActionCreatedAtAfter(paging,dateAfter) - .map(actionDto -> toActionResponse(actionDto, saveInterval)) - .collectList() - .map(this::toActionsListResponse) - .onErrorResume(ProblemException.class, - ex -> { - Logger.errorLog(xRequestId,"List actions cannot be created", null ); - return getError("List actions cannot be created"); - }); - } - - /** - * Delete actions for a given userId and action is create after hours - * @param userId the id of the user for which the action should be deleted - * @param deleteAfterHours hours after the actions should be deleted - * @param xRequestId from the request header. Will be used in an error log - * @return If successful empty Mono object, otherwise Mono error - */ - public Mono deleteUserActions(String userId, Integer deleteAfterHours, String xRequestId ){ - var dateAfter = Date.from(ZonedDateTime.now().minusHours(deleteAfterHours).toInstant()); - return repository - .deleteAllByUserIdAndActionCreatedAtIsBefore(userId, dateAfter) - .map(resp -> new Object()) - .onErrorResume(ProblemException.class,ex -> { - Logger.errorLog(xRequestId,"Deletion of actions cannot be executed for user", userId ); - return Mono.error(ex); - }); - } - - /** - * Delete actions after hours. This service will be used in the cron job. The job will be implemented with a separate user story. - * @param deleteAfterHours hours after the actions should be deleted - * @return If successful empty Mono object, otherwise Mono error - */ - public Mono deleteActions(Integer deleteAfterHours ){ - var dateAfter = Date.from(LocalDateTime.now().minusHours(deleteAfterHours).atZone(ZoneId.of("CET")).toInstant()); - return repository - .deleteAllByActionCreatedAtIsBefore(dateAfter) - .map(resp -> new Object()) - .onErrorResume(ProblemException.class,ex -> { - Logger.errorLog(null,"Delete all actions in cron job cannot be executed ", null); - return getError("Delete all actions after hours cannot be executed"); - }); - } - - /** - * - * @param resp List of ActionResponses - * @param saveInterval value will be part of the response action object. This value is set in the application properties. - * @return ActionsListResponse - */ - private ActionsListResponse toActionsListResponse(java.util.List actionResponses) { - var actionsListResponse = new ActionsListResponse(); - actionsListResponse.setActionsList(actionResponses); - actionsListResponse.setTotalCount(actionResponses.size()); - return actionsListResponse; - } - - /** - * - * @param actionsDao ActionsDao, return from the MongoDB repository query - * @param saveInterval value will be part of the response action object. This value is set in the application properties. - * @return action response object - */ - public ActionResponse toActionResponse(ActionsDao actionsDao, Integer saveInterval){ - return new ActionResponse() - .actionCreatedAt(actionsDao.getActionCreatedAt().toInstant().atOffset(ZoneOffset.ofHours(0))) - .saveInterval(saveInterval) - .action(actionsDao.getAction()); - } - - private ActionsDao toActionsDao(String userId, CreateActionRequest createActionRequest) { - var actionsDao = new ActionsDao(); - actionsDao.setUserId(userId); - actionsDao.setActionCreatedAt(new Date(createActionRequest.getActionCreatedAt().toEpochSecond()*1000)); - actionsDao.setAction(createActionRequest.getAction()); - return actionsDao; - } - - /** - * Build a problem exception with given message - * @param message will be detail part of the problem object - * @return Mono error with problem exception - */ - private Mono getError(String message) { - return Mono.error(ProblemException.builder() - .type(Problem.DEFAULT_TYPE) - .status(Status.BAD_REQUEST) - .title(HttpStatus.BAD_REQUEST.toString()) - .detail(message) - .build()); - } - -} diff --git a/app/src/main/java/org/onap/portal/history/util/IdTokenExchange.java b/app/src/main/java/org/onap/portal/history/util/IdTokenExchange.java deleted file mode 100644 index 82cc67a..0000000 --- a/app/src/main/java/org/onap/portal/history/util/IdTokenExchange.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.util; - -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.JWTParser; - -import java.text.ParseException; - -import org.onap.portal.history.exception.ProblemException; -import org.springframework.web.server.ServerWebExchange; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import reactor.core.publisher.Mono; - -/** - * Represents a function that handles the JWT identity token. - * Use this to check if the incoming requests are authorized to call the given endpoint - */ - -public final class IdTokenExchange { - - public static final String X_AUTH_IDENTITY_HEADER = "X-Auth-Identity"; - public static final String JWT_CLAIM_USERID = "sub"; - - private IdTokenExchange(){ - - } - - /** - * Extract the identity header from the given {@link ServerWebExchange}. - * @param exchange the ServerWebExchange that contains information about the incoming request - * @param xRequestId the id of the request to use in error log - * @return the identity header in the form of Bearer {@literal } - */ - private static Mono extractIdentityHeader(ServerWebExchange exchange, String xRequestId) { - return Mono.just(exchange.getRequest().getHeaders().getOrEmpty(X_AUTH_IDENTITY_HEADER)) - .map(headers -> headers.get(0)) - .onErrorResume(Exception.class, ex -> Mono.error(ProblemException.builder() - .type(Problem.DEFAULT_TYPE) - .status(Status.FORBIDDEN) - .title("Forbidden access") - .detail(X_AUTH_IDENTITY_HEADER + " is not set") - .build())); - } - - /** - * Extract the identity token from the given {@link ServerWebExchange}. - * @see OpenId Connect ID Token - * @param exchange the ServerWebExchange that contains information about the incoming request - * @param xRequestId the id of the request to use in error log - * @return the identity token that contains user roles - */ - private static Mono extractIdToken(ServerWebExchange exchange, String xRequestId) { - return extractIdentityHeader(exchange, xRequestId) - .map(identityHeader -> identityHeader.replace("Bearer ", "")); - } - - /** - * Extract the userId from the given {@link ServerWebExchange} - * @param exchange the ServerWebExchange that contains information about the incoming request - * @param xRequestId the id of the request to use in error log - * @return the id of the user - */ - public static Mono extractUserId(ServerWebExchange exchange,String xRequestId) { - return extractIdToken(exchange, xRequestId) - .flatMap(idToken -> extractUserClaim(idToken)); - } - - private static Mono extractUserClaim(String idToken) { - JWTClaimsSet jwtClaimSet; - try { - jwtClaimSet = JWTParser.parse(idToken).getJWTClaimsSet(); - } catch (ParseException e) { - return Mono.error(e); - } - return Mono.just(String.class.cast(jwtClaimSet.getClaim(JWT_CLAIM_USERID))); - } - - - /** - * Validate if given userId is same as extracted from the given {@link ServerWebExchange} - * @param userId from the path parameter of the REST call - * @param exchange the ServerWebExchange that contains information about the incoming request - * @param xRequestId the id of the request to use in error log - * @return empty Mono userId is the same as extracted from {@link ServerWebExchange} - * Forbidden userId is not the same as extracted from {@link ServerWebExchange} - */ - public static Mono validateUserId(String userId, ServerWebExchange exchange, String xRequestId){ - - return extractUserId(exchange, xRequestId) - .map(userSub -> userSub.equals(userId)) - .flatMap( match -> { - if (Boolean.TRUE.equals(match)) { - return Mono.empty(); - } else{ - Logger.errorLog(xRequestId,"Requested "+ userId + " did not match the JWT in the X-Auth-Identity header" , userId ); - return Mono.error(ProblemException.builder() - .type(Problem.DEFAULT_TYPE) - .status(Status.FORBIDDEN) - .title("Forbidden access") - .detail("UserId did not match with JWT in " + X_AUTH_IDENTITY_HEADER) - .build()); - } - }); - } -} diff --git a/app/src/main/java/org/onap/portal/history/util/Logger.java b/app/src/main/java/org/onap/portal/history/util/Logger.java deleted file mode 100644 index c072c16..0000000 --- a/app/src/main/java/org/onap/portal/history/util/Logger.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.util; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; - -import java.net.URI; - -@Slf4j -public class Logger { - - private Logger(){} - - /** - * Write log to stdout for incoming request - * @param xRequestId from the request header - * @param methode http methode which is invoke - * @param path which is called be the request - */ - public static void requestLog(String xRequestId, HttpMethod methode, URI path) { - log.info("History - request - X-Request-Id {} {} {}", xRequestId, methode, path); - } - - /** - * Write log to stdout for the outgoing response - * @param xRequestId from the request header - * @param code http status of the response - */ - public static void responseLog(String xRequestId, HttpStatusCode httpStatusCode) { - log.info("History - response - X-Request-Id {} {}", xRequestId, httpStatusCode); - } - - /** - * Write error log to stdout - * @param xRequestId from the request header - * @param msg message which should be written - * @param id of the related object of the message - */ - public static void errorLog(String xRequestId, String msg, String id) { - log.info( - "History - error - X-Request-Id {} {} {} not found", xRequestId, msg, id); - } -} diff --git a/app/src/main/java/org/onap/portalng/history/HistoryApplication.java b/app/src/main/java/org/onap/portalng/history/HistoryApplication.java new file mode 100644 index 0000000..1a9cd8e --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/HistoryApplication.java @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package org.onap.portalng.history; + + +import org.onap.portalng.history.configuration.HistoryConfig; +import org.onap.portalng.history.logging.LoggerProperties; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@SpringBootApplication +@EnableConfigurationProperties({HistoryConfig.class, LoggerProperties.class}) +public class HistoryApplication { + public static void main(String[] args) { + SpringApplication.run(HistoryApplication.class, args); + } +} diff --git a/app/src/main/java/org/onap/portalng/history/configuration/BeansConfig.java b/app/src/main/java/org/onap/portalng/history/configuration/BeansConfig.java new file mode 100644 index 0000000..a1fb5f2 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/configuration/BeansConfig.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.configuration; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.zalando.problem.jackson.ProblemModule; + +import java.time.Clock; + +@Configuration +public class BeansConfig { + @Bean + Clock clock() { + return Clock.systemUTC(); + } + + @Bean + public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { + return builder + .modules(new ProblemModule(), new JavaTimeModule()) + .build() + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + +} diff --git a/app/src/main/java/org/onap/portalng/history/configuration/Errorhandler.java b/app/src/main/java/org/onap/portalng/history/configuration/Errorhandler.java new file mode 100644 index 0000000..515935b --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/configuration/Errorhandler.java @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.configuration; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.onap.portalng.history.exception.ProblemException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.zalando.problem.Problem; +import org.zalando.problem.Status; +import reactor.core.publisher.Mono; + +@Component +public class Errorhandler implements ErrorWebExceptionHandler { + + @Autowired + ObjectMapper objectMapper; + + /** + * Override the handle methode to implement custom error handling + * Set response status code to BAD REQUEST, set header content-type and fill the body with the Problem object along the API model + */ + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) { + ServerHttpResponse httpResponse = exchange.getResponse(); + setResponseStatus(httpResponse, ex); + httpResponse.getHeaders().add("Content-Type", "application/problem+json"); + return httpResponse.writeWith(Mono.fromSupplier(() -> { + DataBufferFactory bufferFactory = httpResponse.bufferFactory(); + try { + return + (httpResponse.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) + ? httpResponse.bufferFactory().wrap(objectMapper.writeValueAsBytes(setProblemException(httpResponse, ex.getMessage()))) + : httpResponse.bufferFactory().wrap(objectMapper.writeValueAsBytes(ex)); + } catch (JsonProcessingException e) { + return bufferFactory.wrap(new byte[0]); + } + })); + } + + /** + * Set the response status + * @param httpResponse response which status code should be set + * @param ex throwable exception to identify the Problem class + */ + private void setResponseStatus(ServerHttpResponse httpResponse, Throwable ex) { + if (ex instanceof Problem) { + httpResponse.setStatusCode(HttpStatus.BAD_REQUEST); + } else { + httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Build a problem exception and set the response status code to BAD REQUEST for every response + * @param httpResponse response which status code should be set + * @param message for the detail of the problem exception + * @return problem exception instance + */ + private ProblemException setProblemException(ServerHttpResponse httpResponse, String message){ + httpResponse.setStatusCode(HttpStatus.BAD_REQUEST); + return ProblemException.builder() + .status(Status.INTERNAL_SERVER_ERROR) + .title(Status.INTERNAL_SERVER_ERROR.getReasonPhrase()) + .detail(message) + .build(); + + } +} diff --git a/app/src/main/java/org/onap/portalng/history/configuration/HistoryConfig.java b/app/src/main/java/org/onap/portalng/history/configuration/HistoryConfig.java new file mode 100644 index 0000000..c790288 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/configuration/HistoryConfig.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.configuration; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import jakarta.validation.constraints.NotBlank; + +@Data +@ConfigurationProperties("history") +public class HistoryConfig { + + @NotBlank + private final Integer saveInterval; +} diff --git a/app/src/main/java/org/onap/portalng/history/configuration/RequestIdInterceptor.java b/app/src/main/java/org/onap/portalng/history/configuration/RequestIdInterceptor.java new file mode 100644 index 0000000..291e5e6 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/configuration/RequestIdInterceptor.java @@ -0,0 +1,56 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.configuration; + +import org.onap.portalng.history.util.Logger; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.reactive.ServerWebExchangeContextFilter; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Component +public class RequestIdInterceptor implements WebFilter { + public static final String EXCHANGE_CONTEXT_ATTRIBUTE = ServerWebExchangeContextFilter.class.getName() + + ".EXCHANGE_CONTEXT"; + + public static final String X_REQUEST_ID = "X-Request-Id"; + + /** + * Override a web filter to write log entries for every request and response and + * add header in response with X_REQUEST_ID + */ + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + List xRequestIdList = exchange.getRequest().getHeaders().get(X_REQUEST_ID); + if (xRequestIdList != null && !xRequestIdList.isEmpty()) { + String xRequestId = xRequestIdList.get(0); + exchange.getResponse().getHeaders().add(X_REQUEST_ID, xRequestId); + } + return chain + .filter(exchange) + .contextWrite(cxt -> cxt.put(EXCHANGE_CONTEXT_ATTRIBUTE, exchange)); + } +} diff --git a/app/src/main/java/org/onap/portalng/history/configuration/SchedulerConfig.java b/app/src/main/java/org/onap/portalng/history/configuration/SchedulerConfig.java new file mode 100644 index 0000000..81299b1 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/configuration/SchedulerConfig.java @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.configuration; + +import lombok.extern.slf4j.Slf4j; + +import org.onap.portalng.history.services.ActionsService; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@EnableScheduling +public class SchedulerConfig { + + private final ActionsService actionsService; + private final HistoryConfig historyConfig; + + public SchedulerConfig(ActionsService actionsService, HistoryConfig historyConfig){ + this.actionsService = actionsService; + this.historyConfig = historyConfig; + } + + /** + * This method will be trigger by Spring Boot scheduler. + * The cron execution time is configured in the application properties as well as the save interval. + */ + @Scheduled(cron="${history.delete-interval}") + public void runDeleteActions(){ + actionsService.deleteActions(historyConfig.getSaveInterval()); + log.info("Delete actions in scheduled job"); + } +} diff --git a/app/src/main/java/org/onap/portalng/history/configuration/SecurityConfig.java b/app/src/main/java/org/onap/portalng/history/configuration/SecurityConfig.java new file mode 100644 index 0000000..9d17bba --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/configuration/SecurityConfig.java @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.web.server.SecurityWebFilterChain; + +/** + * Configures the access control of the API endpoints. + */ +// https://hantsy.github.io/spring-reactive-sample/security/config.html +@EnableWebFluxSecurity +@Configuration +public class SecurityConfig { + + @Bean + public SecurityWebFilterChain springSecurityWebFilterChain(ServerHttpSecurity http) { + return http.httpBasic().disable() + .formLogin().disable() + .csrf().disable() + .cors() + .and() + .authorizeExchange() + .pathMatchers(HttpMethod.GET, "/actuator/**").permitAll() + .anyExchange().authenticated() + .and() + .oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt) + .build(); + } +} diff --git a/app/src/main/java/org/onap/portalng/history/controller/ActionsController.java b/app/src/main/java/org/onap/portalng/history/controller/ActionsController.java new file mode 100644 index 0000000..dbab575 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/controller/ActionsController.java @@ -0,0 +1,84 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.controller; + +import java.util.Optional; + +import org.onap.portalng.history.openapi.api.ActionsApi; +import org.onap.portalng.history.openapi.model.ActionResponse; +import org.onap.portalng.history.openapi.model.ActionsListResponse; +import org.onap.portalng.history.openapi.model.CreateActionRequest; +import org.onap.portalng.history.configuration.HistoryConfig; +import org.onap.portalng.history.services.ActionsService; +import org.onap.portalng.history.util.IdTokenExchange; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; + +import reactor.core.publisher.Mono; + +@RestController +public class ActionsController implements ActionsApi { + + private final ActionsService actionsService; + private final HistoryConfig historyConfig; + + public ActionsController(ActionsService actionsService, HistoryConfig historyConfig){ + this.actionsService = actionsService; + this.historyConfig = historyConfig; + } + + @Override + public Mono> createAction(String userId, String xRequestId, Mono createActionRequest, ServerWebExchange exchange) { + + return IdTokenExchange + .validateUserId(userId, exchange, xRequestId) + .then(createActionRequest.flatMap(action -> actionsService.createActions(userId, action, historyConfig.getSaveInterval(), xRequestId))) + .map(ResponseEntity::ok); + } + + @Override + public Mono> deleteActions(String userId, String xRequestId, Integer deleteAfterHours, ServerWebExchange exchange) { + + return IdTokenExchange + .validateUserId(userId, exchange, xRequestId) + .then(actionsService.deleteUserActions(userId, deleteAfterHours, xRequestId)) + .map(ResponseEntity::ok); + } + + @Override + public Mono> getActions(String userId, String xRequestId, Optional page, Optional pageSize, Optional showLastHours, ServerWebExchange exchange) { + + return IdTokenExchange + .validateUserId(userId, exchange, xRequestId) + .then(actionsService.getActions(userId, page.orElse(1), pageSize.orElse(10), showLastHours.orElse(historyConfig.getSaveInterval()), historyConfig.getSaveInterval(), xRequestId)) + .map(ResponseEntity::ok); + } + + @Override + public Mono> listActions(String xRequestId, Optional page, Optional pageSize, Optional showLastHours, ServerWebExchange exchange) { + + return actionsService + .listActions(page.orElse(1), pageSize.orElse(10), showLastHours.orElse(historyConfig.getSaveInterval()), historyConfig.getSaveInterval(), xRequestId) + .map(ResponseEntity::ok); + } +} diff --git a/app/src/main/java/org/onap/portalng/history/entities/ActionsDao.java b/app/src/main/java/org/onap/portalng/history/entities/ActionsDao.java new file mode 100644 index 0000000..127e093 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/entities/ActionsDao.java @@ -0,0 +1,44 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.entities; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.mongodb.core.mapping.Document; +import java.util.Date; + +/** + * Data access object for the actions in the MongoDB repository. + * No database id is set in this class because MongoDB use internal _id as primary key / uniq object identifier + */ +@Document(collection = "actions") +@Getter +@Setter +public class ActionsDao { + + private String userId; + + private Date actionCreatedAt; + + private Object action; + +} diff --git a/app/src/main/java/org/onap/portalng/history/exception/ProblemException.java b/app/src/main/java/org/onap/portalng/history/exception/ProblemException.java new file mode 100644 index 0000000..8c71ce4 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/exception/ProblemException.java @@ -0,0 +1,55 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.exception; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.zalando.problem.AbstractThrowableProblem; +import org.zalando.problem.Problem; +import org.zalando.problem.Status; +import org.zalando.problem.StatusType; + +import java.net.URI; + +/** + * Default problem exception. This class has the same structure as the problem response model from the api. + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProblemException extends AbstractThrowableProblem { + @Builder.Default private final URI type = Problem.DEFAULT_TYPE; + + @Builder.Default private final String title = "Bad history error"; + + @Builder.Default private final StatusType status = Status.BAD_REQUEST; + + @Builder.Default private final String detail = "Please add more details here"; + + @Builder.Default private final URI instance = null; + +} diff --git a/app/src/main/java/org/onap/portalng/history/logging/LogContextVariable.java b/app/src/main/java/org/onap/portalng/history/logging/LogContextVariable.java new file mode 100644 index 0000000..1b1c505 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/logging/LogContextVariable.java @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2023. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.logging; + +import lombok.Getter; + +@Getter +public enum LogContextVariable { + TRACE_ID("trace_id"), + STATUS("status"), + NORTHBOUND_METHOD("northbound.method"), + NORTHBOUND_URL("northbound.url"), + EXECUTION_TIME("execution.time_ms"), + HTTP_STATUS("httpStatus"); + + private final String variableName; + + LogContextVariable(String variableName) { + this.variableName = variableName; + } +} diff --git a/app/src/main/java/org/onap/portalng/history/logging/LoggerProperties.java b/app/src/main/java/org/onap/portalng/history/logging/LoggerProperties.java new file mode 100644 index 0000000..f785822 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/logging/LoggerProperties.java @@ -0,0 +1,11 @@ +package org.onap.portalng.history.logging; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("logger") +public record LoggerProperties( + String traceIdHeaderName, + Boolean enabled, List excludePaths) { +} \ No newline at end of file diff --git a/app/src/main/java/org/onap/portalng/history/logging/LoggingHelper.java b/app/src/main/java/org/onap/portalng/history/logging/LoggingHelper.java new file mode 100644 index 0000000..90c61c4 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/logging/LoggingHelper.java @@ -0,0 +1,68 @@ +/* + * + * Copyright (c) 2023. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.logging; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.MDC; + +import java.util.Map; +import java.util.function.BiConsumer; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LoggingHelper { + public static void error( + Logger logger, Map metadata, String message, Object... args) { + log(logger::error, metadata, message, args); + } + + public static void debug( + Logger logger, Map metadata, String message, Object... args) { + log(logger::debug, metadata, message, args); + } + + public static void info( + Logger logger, Map metadata, String message, Object... args) { + log(logger::info, metadata, message, args); + } + + public static void warn( + Logger logger, Map metadata, String message, Object... args) { + log(logger::warn, metadata, message, args); + } + + public static void trace( + Logger logger, Map metadata, String message, Object... args) { + log(logger::trace, metadata, message, args); + } + + private static void log( + BiConsumer logMethod, + Map metadata, + String message, + Object... args) { + metadata.forEach((variable, value) -> MDC.put(variable.getVariableName(), value)); + logMethod.accept(message, args); + MDC.clear(); + } +} diff --git a/app/src/main/java/org/onap/portalng/history/logging/ReactiveRequestLoggingFilter.java b/app/src/main/java/org/onap/portalng/history/logging/ReactiveRequestLoggingFilter.java new file mode 100644 index 0000000..91df0c1 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/logging/ReactiveRequestLoggingFilter.java @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2023. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.logging; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +public class ReactiveRequestLoggingFilter implements WebFilter { + + private final LoggerProperties loggerProperties; + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + if (loggingDisabled(exchange)) { + return chain.filter(exchange); + } + + var logMessageMetadata = + WebExchangeUtils.getRequestMetadata(exchange, loggerProperties.traceIdHeaderName()); + + LoggingHelper.info(log, logMessageMetadata, "RECEIVED"); + + var invocationStart = LocalDateTime.now(); + return chain + .filter(exchange) + .doOnTerminate( + () -> { + logMessageMetadata.put( + LogContextVariable.STATUS, + exchange.getResponse().getStatusCode().isError() + ? StatusCode.ERROR.name() + : StatusCode.COMPLETE.name()); + logMessageMetadata.put( + LogContextVariable.HTTP_STATUS, + String.valueOf(exchange.getResponse().getStatusCode().value())); + logMessageMetadata.put( + LogContextVariable.EXECUTION_TIME, + String.valueOf( + Duration.between(invocationStart, LocalDateTime.now()).toMillis())); + }) + .doOnSuccess(res -> LoggingHelper.info(log, logMessageMetadata, "FINISHED")) + .doOnError( + ex -> LoggingHelper.warn(log, logMessageMetadata, "FAILED: {}", ex.getMessage())); + } + + private boolean loggingDisabled(ServerWebExchange exchange) { + boolean loggingDisabled = loggerProperties.enabled() == null || !loggerProperties.enabled(); + + boolean urlShouldBeSkipped = + WebExchangeUtils.matchUrlsPatternsToPath(loggerProperties.excludePaths(), exchange.getRequest().getPath().value()); + + return loggingDisabled || urlShouldBeSkipped; + } +} diff --git a/app/src/main/java/org/onap/portalng/history/logging/StatusCode.java b/app/src/main/java/org/onap/portalng/history/logging/StatusCode.java new file mode 100644 index 0000000..9feae4d --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/logging/StatusCode.java @@ -0,0 +1,28 @@ +/* + * + * Copyright (c) 2023. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.logging; + +public enum StatusCode { + REQUEST, + COMPLETE, + ERROR +} diff --git a/app/src/main/java/org/onap/portalng/history/logging/WebExchangeUtils.java b/app/src/main/java/org/onap/portalng/history/logging/WebExchangeUtils.java new file mode 100644 index 0000000..c92564e --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/logging/WebExchangeUtils.java @@ -0,0 +1,92 @@ +/* + * + * Copyright (c) 2023. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.logging; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import org.springframework.web.server.ServerWebExchange; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class WebExchangeUtils { + private static final String DEFAULT_TRACE_ID = "REQUEST_ID_IS_NOT_SET"; + private static final String DEFAULT_REQUEST_URL = "REQUEST_URL_IS_ABSENT"; + private static final String DEFAULT_REQUEST_METHOD = "HTTP_METHOD_IS_ABSENT"; + + private static final PathMatcher pathMatcher = new AntPathMatcher(); + + public static String getRequestId(ServerWebExchange webExchange, String traceIdHeaderName) { + if (webExchange == null || traceIdHeaderName == null) { + return DEFAULT_TRACE_ID; + } + + var requestIdHeaders = webExchange.getRequest().getHeaders().get(traceIdHeaderName); + if (requestIdHeaders != null) { + return requestIdHeaders.stream().findAny().orElse(DEFAULT_TRACE_ID); + } else { + return DEFAULT_TRACE_ID; + } + } + + public static String getRequestUrl(ServerWebExchange webExchange) { + if (webExchange == null) { + return DEFAULT_REQUEST_URL; + } + return webExchange.getRequest().getURI().toString(); + } + + public static String getRequestHttpMethod(ServerWebExchange webExchange) { + if (webExchange == null) { + return DEFAULT_REQUEST_METHOD; + } + return webExchange.getRequest().getMethod().name(); + } + + public static boolean matchUrlPatternToPath(String pattern, String path) { + return pathMatcher.match(pattern, path); + } + + public static boolean matchUrlsPatternsToPath(List patterns, String path) { + return patterns != null + && patterns.stream().anyMatch(pathPattern -> matchUrlPatternToPath(pathPattern, path)); + } + + public static Map getRequestMetadata( + ServerWebExchange exchange, String traceIdHeaderName) { + var traceId = WebExchangeUtils.getRequestId(exchange, traceIdHeaderName); + var requestMethod = WebExchangeUtils.getRequestHttpMethod(exchange); + var requestUrl = WebExchangeUtils.getRequestUrl(exchange); + + var logMessageMetadata = new EnumMap(LogContextVariable.class); + logMessageMetadata.put(LogContextVariable.TRACE_ID, traceId); + logMessageMetadata.put(LogContextVariable.STATUS, StatusCode.REQUEST.name()); + logMessageMetadata.put(LogContextVariable.NORTHBOUND_METHOD, requestMethod); + logMessageMetadata.put(LogContextVariable.NORTHBOUND_URL, requestUrl); + + return logMessageMetadata; + } +} diff --git a/app/src/main/java/org/onap/portalng/history/repository/ActionsRepository.java b/app/src/main/java/org/onap/portalng/history/repository/ActionsRepository.java new file mode 100644 index 0000000..d0dcc44 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/repository/ActionsRepository.java @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.repository; + +import java.util.Date; + +import org.onap.portalng.history.entities.ActionsDao; +import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface ActionsRepository extends ReactiveMongoRepository { + + Flux findAllByActionCreatedAtAfter(Pageable pageable, Date actionCreatedAt); + + Flux findAllByUserIdAndActionCreatedAtAfter(Pageable pageable, String userId, Date actionCreatedAt); + + Mono deleteAllByUserIdAndActionCreatedAtIsBefore(String userId, Date actionCreatedAt); + + Mono deleteAllByActionCreatedAtIsBefore(Date actionCreatedAt); +} diff --git a/app/src/main/java/org/onap/portalng/history/services/ActionsService.java b/app/src/main/java/org/onap/portalng/history/services/ActionsService.java new file mode 100644 index 0000000..746b108 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/services/ActionsService.java @@ -0,0 +1,218 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.services; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Date; + +import org.onap.portalng.history.openapi.model.ActionResponse; +import org.onap.portalng.history.openapi.model.ActionsListResponse; +import org.onap.portalng.history.openapi.model.CreateActionRequest; +import org.onap.portalng.history.entities.ActionsDao; +import org.onap.portalng.history.exception.ProblemException; +import org.onap.portalng.history.repository.ActionsRepository; +import org.onap.portalng.history.util.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.zalando.problem.Problem; +import org.zalando.problem.Status; + +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + + +@Slf4j +@Service +public class ActionsService { + + @Autowired + private ActionsRepository repository; + + /** + * Retrieve actions for a given userId from the database and provide a list with actions + * @param userId only actions for this userId should be retrieved + * @param page which page should be retrieved from the list of actions. From a user perspective the first page has the page number 1. + * In the response list the first page starts with 0. Therefore, a subtraction is needed. + * @param pageSize length of the response list + * @param showLastHours for which hours from the current time the actions should be retrieved. + * @param saveInterval value will be part of the response action object. This value is set in the application properties. + * In the future this value can be provided from the client. + * @param xRequestId from the request header. Will be used in an error log + * @return If successful object with an item list of action objects and an item with the list count, otherwise Mono error + */ + public Mono getActions(String userId, Integer page, Integer pageSize, Integer showLastHours, Integer saveInterval, String xRequestId){ + Pageable paging = PageRequest.of(page - 1 , pageSize, Sort.by(Sort.Direction.DESC, "actionCreatedAt")); + var dateAfter = Date.from(ZonedDateTime.now().minusHours(showLastHours).toInstant()); + return repository + .findAllByUserIdAndActionCreatedAtAfter(paging,userId, dateAfter) + .map(actionDao -> toActionResponse(actionDao, saveInterval)) + .collectList() + .map(this::toActionsListResponse) + .switchIfEmpty(Mono.just(new ActionsListResponse().totalCount(0))) + .onErrorResume(ex -> { + Logger.errorLog(xRequestId,"Get actions cannot be executed for user with id ", userId); + return getError("Get actions can not be executed for user with id " + userId); + }); + } + + /** + * Create an action data record in the database + * @param userId the id of the user for which the action should be stored + * @param createActionRequest the action object which should be stored + * @param saveInterval value will be part of the response action object. This value is set in the application properties. + * In the future this value can be provided from the client. + * @param xRequestId from the request header. Will be used in an error log + * @return If successful object with the stored action, otherwise Mono error + */ + public Mono createActions(String userId, CreateActionRequest createActionRequest, Integer saveInterval, String xRequestId) { + return repository + .save(toActionsDao(userId, createActionRequest)) + .map(action -> toActionResponse(action, saveInterval)) + .onErrorResume(ex -> { + Logger.errorLog(xRequestId,"Action for user can not be executed for user with id ", userId ); + return Mono.error(ProblemException.builder() + .type(Problem.DEFAULT_TYPE) + .status(Status.BAD_REQUEST) + .title(HttpStatus.BAD_REQUEST.toString()) + .detail("Action for user can not be executed for user with id " + userId) + .build()); + }); + } + + /** + * List all actions without a userId filter. + * @param page which page should be retrieved from the list of actions. From a user perspective the first page has the page number 1. + * In the response list the first page starts with 0. Therefore, a subtraction is needed. + * @param pageSize length of the response list + * @param showLastHours for which hours from the current time the actions should be retrieved. + * @param saveInterval value will be part of the response action object. This value is set in the application properties. + * * In the future this value can be provided from the client. + * @param xRequestId from the request header. Will be used in an error log + * @return If successful list with action response object, otherwise Mono error + */ + public Mono listActions(Integer page, Integer pageSize, Integer showLastHours, Integer saveInterval, String xRequestId){ + + var paging = PageRequest.of(page - 1 , pageSize, Sort.by(Sort.Direction.DESC, "actionCreatedAt")); + var dateAfter = Date.from(ZonedDateTime.now().minusHours(showLastHours).toInstant()); + + return repository + .findAllByActionCreatedAtAfter(paging,dateAfter) + .map(actionDto -> toActionResponse(actionDto, saveInterval)) + .collectList() + .map(this::toActionsListResponse) + .onErrorResume(ProblemException.class, + ex -> { + Logger.errorLog(xRequestId,"List actions cannot be created", null ); + return getError("List actions cannot be created"); + }); + } + + /** + * Delete actions for a given userId and action is create after hours + * @param userId the id of the user for which the action should be deleted + * @param deleteAfterHours hours after the actions should be deleted + * @param xRequestId from the request header. Will be used in an error log + * @return If successful empty Mono object, otherwise Mono error + */ + public Mono deleteUserActions(String userId, Integer deleteAfterHours, String xRequestId ){ + var dateAfter = Date.from(ZonedDateTime.now().minusHours(deleteAfterHours).toInstant()); + return repository + .deleteAllByUserIdAndActionCreatedAtIsBefore(userId, dateAfter) + .map(resp -> new Object()) + .onErrorResume(ProblemException.class,ex -> { + Logger.errorLog(xRequestId,"Deletion of actions cannot be executed for user", userId ); + return Mono.error(ex); + }); + } + + /** + * Delete actions after hours. This service will be used in the cron job. The job will be implemented with a separate user story. + * @param deleteAfterHours hours after the actions should be deleted + * @return If successful empty Mono object, otherwise Mono error + */ + public Mono deleteActions(Integer deleteAfterHours ){ + var dateAfter = Date.from(LocalDateTime.now().minusHours(deleteAfterHours).atZone(ZoneId.of("CET")).toInstant()); + return repository + .deleteAllByActionCreatedAtIsBefore(dateAfter) + .map(resp -> new Object()) + .onErrorResume(ProblemException.class,ex -> { + Logger.errorLog(null,"Delete all actions in cron job cannot be executed ", null); + return getError("Delete all actions after hours cannot be executed"); + }); + } + + /** + * + * @param resp List of ActionResponses + * @param saveInterval value will be part of the response action object. This value is set in the application properties. + * @return ActionsListResponse + */ + private ActionsListResponse toActionsListResponse(java.util.List actionResponses) { + var actionsListResponse = new ActionsListResponse(); + actionsListResponse.setActionsList(actionResponses); + actionsListResponse.setTotalCount(actionResponses.size()); + return actionsListResponse; + } + + /** + * + * @param actionsDao ActionsDao, return from the MongoDB repository query + * @param saveInterval value will be part of the response action object. This value is set in the application properties. + * @return action response object + */ + public ActionResponse toActionResponse(ActionsDao actionsDao, Integer saveInterval){ + return new ActionResponse() + .actionCreatedAt(actionsDao.getActionCreatedAt().toInstant().atOffset(ZoneOffset.ofHours(0))) + .saveInterval(saveInterval) + .action(actionsDao.getAction()); + } + + private ActionsDao toActionsDao(String userId, CreateActionRequest createActionRequest) { + var actionsDao = new ActionsDao(); + actionsDao.setUserId(userId); + actionsDao.setActionCreatedAt(new Date(createActionRequest.getActionCreatedAt().toEpochSecond()*1000)); + actionsDao.setAction(createActionRequest.getAction()); + return actionsDao; + } + + /** + * Build a problem exception with given message + * @param message will be detail part of the problem object + * @return Mono error with problem exception + */ + private Mono getError(String message) { + return Mono.error(ProblemException.builder() + .type(Problem.DEFAULT_TYPE) + .status(Status.BAD_REQUEST) + .title(HttpStatus.BAD_REQUEST.toString()) + .detail(message) + .build()); + } + +} diff --git a/app/src/main/java/org/onap/portalng/history/util/IdTokenExchange.java b/app/src/main/java/org/onap/portalng/history/util/IdTokenExchange.java new file mode 100644 index 0000000..91db5f9 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/util/IdTokenExchange.java @@ -0,0 +1,126 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.util; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTParser; + +import java.text.ParseException; + +import org.onap.portalng.history.exception.ProblemException; +import org.springframework.web.server.ServerWebExchange; +import org.zalando.problem.Problem; +import org.zalando.problem.Status; +import reactor.core.publisher.Mono; + +/** + * Represents a function that handles the JWT identity token. + * Use this to check if the incoming requests are authorized to call the given endpoint + */ + +public final class IdTokenExchange { + + public static final String X_AUTH_IDENTITY_HEADER = "X-Auth-Identity"; + public static final String JWT_CLAIM_USERID = "sub"; + + private IdTokenExchange(){ + + } + + /** + * Extract the identity header from the given {@link ServerWebExchange}. + * @param exchange the ServerWebExchange that contains information about the incoming request + * @param xRequestId the id of the request to use in error log + * @return the identity header in the form of Bearer {@literal } + */ + private static Mono extractIdentityHeader(ServerWebExchange exchange, String xRequestId) { + return Mono.just(exchange.getRequest().getHeaders().getOrEmpty(X_AUTH_IDENTITY_HEADER)) + .map(headers -> headers.get(0)) + .onErrorResume(Exception.class, ex -> Mono.error(ProblemException.builder() + .type(Problem.DEFAULT_TYPE) + .status(Status.FORBIDDEN) + .title("Forbidden access") + .detail(X_AUTH_IDENTITY_HEADER + " is not set") + .build())); + } + + /** + * Extract the identity token from the given {@link ServerWebExchange}. + * @see OpenId Connect ID Token + * @param exchange the ServerWebExchange that contains information about the incoming request + * @param xRequestId the id of the request to use in error log + * @return the identity token that contains user roles + */ + private static Mono extractIdToken(ServerWebExchange exchange, String xRequestId) { + return extractIdentityHeader(exchange, xRequestId) + .map(identityHeader -> identityHeader.replace("Bearer ", "")); + } + + /** + * Extract the userId from the given {@link ServerWebExchange} + * @param exchange the ServerWebExchange that contains information about the incoming request + * @param xRequestId the id of the request to use in error log + * @return the id of the user + */ + public static Mono extractUserId(ServerWebExchange exchange,String xRequestId) { + return extractIdToken(exchange, xRequestId) + .flatMap(idToken -> extractUserClaim(idToken)); + } + + private static Mono extractUserClaim(String idToken) { + JWTClaimsSet jwtClaimSet; + try { + jwtClaimSet = JWTParser.parse(idToken).getJWTClaimsSet(); + } catch (ParseException e) { + return Mono.error(e); + } + return Mono.just(String.class.cast(jwtClaimSet.getClaim(JWT_CLAIM_USERID))); + } + + + /** + * Validate if given userId is same as extracted from the given {@link ServerWebExchange} + * @param userId from the path parameter of the REST call + * @param exchange the ServerWebExchange that contains information about the incoming request + * @param xRequestId the id of the request to use in error log + * @return empty Mono userId is the same as extracted from {@link ServerWebExchange} + * Forbidden userId is not the same as extracted from {@link ServerWebExchange} + */ + public static Mono validateUserId(String userId, ServerWebExchange exchange, String xRequestId){ + + return extractUserId(exchange, xRequestId) + .map(userSub -> userSub.equals(userId)) + .flatMap( match -> { + if (Boolean.TRUE.equals(match)) { + return Mono.empty(); + } else{ + Logger.errorLog(xRequestId,"Requested "+ userId + " did not match the JWT in the X-Auth-Identity header" , userId ); + return Mono.error(ProblemException.builder() + .type(Problem.DEFAULT_TYPE) + .status(Status.FORBIDDEN) + .title("Forbidden access") + .detail("UserId did not match with JWT in " + X_AUTH_IDENTITY_HEADER) + .build()); + } + }); + } +} diff --git a/app/src/main/java/org/onap/portalng/history/util/Logger.java b/app/src/main/java/org/onap/portalng/history/util/Logger.java new file mode 100644 index 0000000..f1cb150 --- /dev/null +++ b/app/src/main/java/org/onap/portalng/history/util/Logger.java @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.util; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; + +import java.net.URI; + +@Slf4j +public class Logger { + + private Logger(){} + + /** + * Write log to stdout for incoming request + * @param xRequestId from the request header + * @param methode http methode which is invoke + * @param path which is called be the request + */ + public static void requestLog(String xRequestId, HttpMethod methode, URI path) { + log.info("History - request - X-Request-Id {} {} {}", xRequestId, methode, path); + } + + /** + * Write log to stdout for the outgoing response + * @param xRequestId from the request header + * @param code http status of the response + */ + public static void responseLog(String xRequestId, HttpStatusCode httpStatusCode) { + log.info("History - response - X-Request-Id {} {}", xRequestId, httpStatusCode); + } + + /** + * Write error log to stdout + * @param xRequestId from the request header + * @param msg message which should be written + * @param id of the related object of the message + */ + public static void errorLog(String xRequestId, String msg, String id) { + log.info( + "History - error - X-Request-Id {} {} {} not found", xRequestId, msg, id); + } +} diff --git a/app/src/test/java/org/onap/portal/history/BaseIntegrationTest.java b/app/src/test/java/org/onap/portal/history/BaseIntegrationTest.java deleted file mode 100644 index feee00c..0000000 --- a/app/src/test/java/org/onap/portal/history/BaseIntegrationTest.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.client.WireMock; -import com.nimbusds.jose.jwk.JWKSet; -import org.onap.portal.history.util.IdTokenExchange; -import io.restassured.RestAssured; -import io.restassured.filter.log.RequestLoggingFilter; -import io.restassured.filter.log.ResponseLoggingFilter; -import io.restassured.specification.RequestSpecification; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; -import org.springframework.http.MediaType; - -import java.util.List; -import java.util.UUID; - -/** Base class for all tests that has the common config including port, realm, logging and auth. */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureWireMock(port = 0) -public abstract class BaseIntegrationTest { - -// @TestConfiguration -// public static class Config { -// @Bean -// WireMockConfigurationCustomizer optionsCustomizer() { -// return options -> options.extensions(new ResponseTemplateTransformer(true)); -// } -// } - - @LocalServerPort protected int port; - @Value("${history.realm}") - protected String realm; - - @Value("${history.delete-interval}") - protected String deleteInterval; - - @Autowired protected ObjectMapper objectMapper; - @Autowired private TokenGenerator tokenGenerator; - - @BeforeAll - public static void setup() { - RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter()); - } - - /** Mocks the OIDC auth flow. */ - @BeforeEach - public void mockAuth() { - WireMock.reset(); - - WireMock.stubFor( - WireMock.get( - WireMock.urlMatching( - String.format("/auth/realms/%s/protocol/openid-connect/certs", realm))) - .willReturn( - WireMock.aResponse() - .withHeader("Content-Type", JWKSet.MIME_TYPE) - .withBody(tokenGenerator.getJwkSet().toString()))); - - final TokenGenerator.TokenGeneratorConfig config = - TokenGenerator.TokenGeneratorConfig.builder().port(port).realm(realm).sub("test-user").build(); - - WireMock.stubFor( - WireMock.post( - WireMock.urlMatching( - String.format("/auth/realms/%s/protocol/openid-connect/token", realm))) - .withBasicAuth("test", "test") - .withRequestBody(WireMock.containing("grant_type=client_credentials")) - .willReturn( - WireMock.aResponse() - .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) - .withBody( - objectMapper - .createObjectNode() - .put("token_type", "bearer") - .put("access_token", tokenGenerator.generateToken(config)) - .put("expires_in", config.getExpireIn().getSeconds()) - .put("refresh_token", tokenGenerator.generateToken(config)) - .put("refresh_expires_in", config.getExpireIn().getSeconds()) - .put("not-before-policy", 0) - .put("session_state", UUID.randomUUID().toString()) - .put("scope", "email profile") - .toString()))); - } - - /** - * Builds an OAuth2 configuration including the roles, port and realm. This config can be used to - * generate OAuth2 access tokens. - * - * @param sub the userId - * @param roles the roles used for RBAC - * @return the OAuth2 configuration - */ - protected TokenGenerator.TokenGeneratorConfig getTokenGeneratorConfig(String sub, List roles) { - return TokenGenerator.TokenGeneratorConfig.builder() - .port(port) - .sub(sub) - .realm(realm) - .roles(roles) - .build(); - } - - /** Get a RequestSpecification that does not have an Identity header. */ - protected RequestSpecification unauthenticatedRequestSpecification() { - return RestAssured.given().port(port); - } - - /** - * Object to store common attributes of requests that are going to be made. Adds an Identity - * header for the onap_admin role to the request. - * @return the definition of the incoming request (northbound) - */ - protected RequestSpecification requestSpecification() { - final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig("test-user", List.of("foo"))); - - return unauthenticatedRequestSpecification() - .auth() - .preemptive() - .oauth2(idToken) - .header(IdTokenExchange.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); - } - - /** - * Object to store common attributes of requests that are going to be made. Adds an Identity - * header for the onap_admin role to the request. - * @param userId the userId that should be contained in the incoming request - * @return the definition of the incoming request (northbound) - */ - protected RequestSpecification requestSpecification(String userId) { - final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(userId, List.of("foo"))); - - return unauthenticatedRequestSpecification() - .auth() - .preemptive() - .oauth2(idToken) - .header(IdTokenExchange.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); - } - - /** - * Object to store common attributes of requests that are going to be made. Adds an Identity - * header for the onap_admin role to the request. - * @param userId the userId that should be contained in the incoming request - * @return the definition of the incoming request (northbound) - */ - protected RequestSpecification wrongHeaderRequestSpecification(String userId) { - final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(userId, List.of("foo"))); - - return unauthenticatedRequestSpecification() - .auth() - .preemptive() - .oauth2(idToken) - .header("X-WRONG-HEADER", "Bearer " + idToken); - } -} diff --git a/app/src/test/java/org/onap/portal/history/TokenGenerator.java b/app/src/test/java/org/onap/portal/history/TokenGenerator.java deleted file mode 100644 index 986507c..0000000 --- a/app/src/test/java/org/onap/portal/history/TokenGenerator.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history; - -import com.nimbusds.jose.JOSEObjectType; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.JWSSigner; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.KeyUse; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; -import lombok.Builder; -import lombok.Getter; -import lombok.NonNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -@Component -public class TokenGenerator { - - private static final String ROLES_CLAIM = "roles"; - private static final String USERID_CLAIM = "sub"; - - private final Clock clock; - private final RSAKey jwk; - private final JWKSet jwkSet; - private final JWSSigner signer; - - @Autowired - public TokenGenerator(Clock clock) { - try { - this.clock = clock; - jwk = - new RSAKeyGenerator(2048) - .keyUse(KeyUse.SIGNATURE) - .keyID(UUID.randomUUID().toString()) - .generate(); - jwkSet = new JWKSet(jwk); - signer = new RSASSASigner(jwk); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public JWKSet getJwkSet() { - return jwkSet; - } - - public String generateToken(TokenGeneratorConfig config) { - final Instant iat = clock.instant(); - final Instant exp = iat.plus(config.expireIn); - - final JWTClaimsSet claims = - new JWTClaimsSet.Builder() - .jwtID(UUID.randomUUID().toString()) - .subject(UUID.randomUUID().toString()) - .issuer(config.issuer()) - .issueTime(Date.from(iat)) - .expirationTime(Date.from(exp)) - .claim(ROLES_CLAIM, config.getRoles()) - .claim(USERID_CLAIM, config.getSub()) - .build(); - - final SignedJWT jwt = - new SignedJWT( - new JWSHeader.Builder(JWSAlgorithm.RS256) - .keyID(jwk.getKeyID()) - .type(JOSEObjectType.JWT) - .build(), - claims); - - try { - jwt.sign(signer); - } catch (Exception e) { - throw new RuntimeException(e); - } - - return jwt.serialize(); - } - - @Getter - @Builder - public static class TokenGeneratorConfig { - private final int port; - - @NonNull private final String sub; - - @NonNull private final String realm; - - @NonNull @Builder.Default private final Duration expireIn = Duration.ofMinutes(5); - - @Builder.Default private final List roles = Collections.emptyList(); - - public String issuer() { - return String.format("http://localhost:%d/auth/realms/%s", port, realm); - } - } -} diff --git a/app/src/test/java/org/onap/portal/history/actions/ActionDto.java b/app/src/test/java/org/onap/portal/history/actions/ActionDto.java deleted file mode 100644 index 2deec8e..0000000 --- a/app/src/test/java/org/onap/portal/history/actions/ActionDto.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.actions; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -public class ActionDto { - String type; - String action; - String message; - String downStreamSystem; - String downStreamId; -} diff --git a/app/src/test/java/org/onap/portal/history/actions/ActionFixtures.java b/app/src/test/java/org/onap/portal/history/actions/ActionFixtures.java deleted file mode 100644 index efab59a..0000000 --- a/app/src/test/java/org/onap/portal/history/actions/ActionFixtures.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.actions; - -import java.time.OffsetDateTime; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.onap.portal.history.entities.ActionsDao; -import org.onap.portal.history.openapi.model.CreateActionRequest; - -public class ActionFixtures { - - public static List createActionRequestList( - Integer numberOfActions, String userId, OffsetDateTime createdAt){ - List createActionRequestList = new ArrayList<>(); - for (Integer i = 1; i <= numberOfActions; i++) { - createActionRequestList.add( - generateActionRequest( - "Instantiation", "create", "action" + i, i.toString(), "SO", i, i, i, userId, createdAt)); - } - return createActionRequestList; - } - - public static List createActionRequestListHourOffsetOnly( - Integer numberOfActions, String userId, OffsetDateTime createdAt){ - List createActionRequestList = new ArrayList<>(); - for (Integer i = 1; i <= numberOfActions; i++) { - createActionRequestList.add( - generateActionRequest( - "Instantiation", "create", "action" + i, i.toString(), "SO", i, 0, 0, userId, createdAt)); - } - return createActionRequestList; - } - - public static CreateActionRequest generateActionRequest( - String type, - String action, - String message, - String id, - String downStreamSystem, - Integer deltaHours, - Integer deltaMinutes, - Integer deltaSeconds, - String userId, - OffsetDateTime createdAt) { - ActionDto actionDto = new ActionDto(); - actionDto.setType(type); - actionDto.setAction(action); - actionDto.setMessage(message); - actionDto.setDownStreamSystem(downStreamSystem); - actionDto.setDownStreamId(id); - - return new CreateActionRequest() - .userId(userId) - .action(actionDto) - .actionCreatedAt(createdAt.minusHours(deltaHours).minusMinutes(deltaMinutes).minusSeconds(deltaSeconds)); - } - - public static List actionsDaoList( - Integer numberOfActions, String userId, OffsetDateTime createdAt){ - List actionsDaoList = new ArrayList<>(); - for (Integer i = 1; i <= numberOfActions; i++) { - actionsDaoList.add( - generateActionsDao( - "Instantiation", "create", "action" + i, i.toString(), "SO", i, i, i, userId, createdAt)); - } - return actionsDaoList; - } - - public static ActionsDao generateActionsDao( - String type, - String action, - String message, - String id, - String downStreamSystem, - Integer deltaHours, - Integer deltaMinutes, - Integer deltaSeconds, - String userId, - OffsetDateTime createdAt) { - ActionDto actionDto = new ActionDto(); - actionDto.setType(type); - actionDto.setAction(action); - actionDto.setMessage(message); - actionDto.setDownStreamSystem(downStreamSystem); - actionDto.setDownStreamId(id); - - ActionsDao actionsDao = new ActionsDao(); - actionsDao.setUserId(userId); - actionsDao.setAction(actionDto); - actionsDao.setActionCreatedAt(new Date(createdAt.minusHours(deltaHours).minusMinutes(deltaMinutes).minusSeconds(deltaSeconds).toEpochSecond()*1000)); - return actionsDao; - } - - public static List actionsDaoListHourOffsetOnly( - Integer numberOfActions, String userId, OffsetDateTime createdAt){ - List actionsDaoList = new ArrayList<>(); - for (Integer i = 1; i <= numberOfActions; i++) { - actionsDaoList.add( - generateActionsDao( - "Instantiation", "create", "action" + i, i.toString(), "SO", i, 0, 0, userId, createdAt)); - } - return actionsDaoList; - } -} diff --git a/app/src/test/java/org/onap/portal/history/actions/ActionsControllerIntegrationTest.java b/app/src/test/java/org/onap/portal/history/actions/ActionsControllerIntegrationTest.java deleted file mode 100644 index feab695..0000000 --- a/app/src/test/java/org/onap/portal/history/actions/ActionsControllerIntegrationTest.java +++ /dev/null @@ -1,571 +0,0 @@ -/* - * - * Copyright (c) 2022. Deutsche Telekom AG - * - * 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 - * - * - */ - -package org.onap.portal.history.actions; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.onap.portal.history.BaseIntegrationTest; -import org.onap.portal.history.entities.ActionsDao; -import org.onap.portal.history.openapi.model.ActionResponse; -import org.onap.portal.history.openapi.model.ActionsListResponse; -import org.onap.portal.history.openapi.model.CreateActionRequest; -import org.onap.portal.history.openapi.model.Problem; -import org.onap.portal.history.repository.ActionsRepository; -import org.onap.portal.history.services.ActionsService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; - -import com.fasterxml.jackson.core.JsonProcessingException; - -import io.restassured.http.Header; - -class ActionsControllerIntegrationTest extends BaseIntegrationTest { - - protected static final String X_REQUEST_ID = "addf6005-3075-4c80-b7bc-2c70b7d42b57"; - protected static final String X_REQUEST_ID2 = "addf6005-3075-4c80-b7bc-2c70b7d42b22"; - - @Autowired - ActionsService actionsService; - - @Autowired - private ActionsRepository repository; - - // @Value("${history.save-interval}") - protected Integer saveInterval = 72; - - @BeforeEach - void deleteMongoDataBase(){ - repository.deleteAll().block(); - } - - @Test - void thatUserCanHaveNoHistoryYet() throws JsonProcessingException { - ActionsListResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions/test-user") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertNotNull(response); - assertThat(response.getTotalCount()).isEqualTo(0); - } - - @Test - void thatActionCanBeSaved() throws Exception{ - ActionDto actionDto = new ActionDto(); - actionDto.setType("instantiation"); - actionDto.setAction("create"); - actionDto.setDownStreamId("1234"); - actionDto.setDownStreamSystem("SO"); - actionDto.setMessage("no details"); - - CreateActionRequest actionRequest = new CreateActionRequest() - .actionCreatedAt(OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)) - .userId("test-user") - .action(actionDto); - - ActionResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .body(actionRequest) - .when() - .post( "/v1/actions/test-user") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionResponse.class); - - assertThat(response.getActionCreatedAt()).isEqualTo(actionRequest.getActionCreatedAt().truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_DATE_TIME)); - assertThat(response.getSaveInterval()).isEqualTo(saveInterval); - assertThat(objectMapper.writeValueAsString(response.getAction())).isEqualTo(objectMapper.writeValueAsString(actionRequest.getAction())); - } - - @Test - void thatActionsCanBeListedWithoutParameter() throws JsonProcessingException { - List actionsDaoList = ActionFixtures.actionsDaoList(500, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - repository - .saveAll(actionsDaoList) - .blockLast(); - ActionsListResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(response.getTotalCount()).isEqualTo(10); - assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); - assertThat(response.getActionsList().get(9).getSaveInterval()).isEqualTo(saveInterval); - assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); - assertThat(objectMapper.writeValueAsString(response.getActionsList().get(9).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(9).getAction())); - } - - @Test - void thatActionsCanBeListedWithParameter() throws JsonProcessingException { - List actionsDaoList = ActionFixtures.actionsDaoList(20, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - repository - .saveAll(actionsDaoList) - .blockLast(); - ActionsListResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions?page=1&pageSize=5") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(response.getTotalCount()).isEqualTo(5); - assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); - assertThat(response.getActionsList().get(4).getSaveInterval()).isEqualTo(saveInterval); - assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); - assertThat(objectMapper.writeValueAsString(response.getActionsList().get(4).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(4).getAction())); - } - - @Test - void thatActionsCanBeListedWithParameterInOrderByActionCreatedAt() { - List actionsDaoList = ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusDays(2), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); - actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(6), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); - actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(12), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); - repository - .saveAll(actionsDaoList) - .blockLast(); - - ActionsListResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions?page=1&pageSize=5") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(response.getTotalCount()).isEqualTo(5); - assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); - assertThat(response.getActionsList().get(4).getSaveInterval()).isEqualTo(saveInterval); - assertThat(response.getActionsList().get(0).getActionCreatedAt()).isEqualTo(actionsDaoList.get(5).getActionCreatedAt().toInstant().atOffset(ZoneOffset.UTC)); - assertThat(response.getActionsList().get(4).getActionCreatedAt()).isEqualTo(actionsDaoList.get(9).getActionCreatedAt().toInstant().atOffset(ZoneOffset.UTC)); - } - - @Test - void thatActionsCanBeListedWithShowLastHours() throws JsonProcessingException { - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(20, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); - repository - .saveAll(actionsDaoList) - .blockLast(); - - ActionsListResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions?page=1&pageSize=20&showLastHours=12") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(response.getTotalCount()).isEqualTo(12); - assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); - assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); - assertThat(objectMapper.writeValueAsString(response.getActionsList().get(11).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(11).getAction())); - } - - @Test - void thatActionsCanNotBeListedWithWrongPageParameter() { - List actionsDaoList = ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusDays(2), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - repository - .saveAll(actionsDaoList) - .blockLast(); - - Problem response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions?page=0&pageSize=5") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.BAD_REQUEST.value()) - .extract() - .body() - .as(Problem.class); - - assertThat(response.getStatus()).isEqualTo(500); - } - - @Test - void thatActionsCanBeGetForUserWithShowLastHours(){ - // First mixed user actions for different users - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); - - actionsDaoList.addAll(actionsDaoList2); - actionsDaoList.addAll(actionsDaoList3); - - repository - .saveAll(actionsDaoList) - .blockLast(); - - ActionsListResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions/test-user?page=1&pageSize=20&showLastHours=2") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(response.getTotalCount()).isEqualTo(2); - assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); - } - - @Test - void thatActionsCanBeGottenForUserWithShowLastHoursWithMinusValue(){ - // First mixed user actions for different users - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().plusHours(48), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - actionsDaoList.addAll(actionsDaoList2); - actionsDaoList.addAll(actionsDaoList3); - actionsDaoList.addAll(actionsDaoList4); - - repository - .saveAll(actionsDaoList) - .blockLast(); - - ActionsListResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions/test-user?page=1&pageSize=20&showLastHours=-2") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(response.getTotalCount()).isEqualTo(10); - } - - @Test - void thatActionsCanBeGottenForUserWithoutParameter(){ - // First mixed user actions for different users - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - actionsDaoList.addAll(actionsDaoList2); - actionsDaoList.addAll(actionsDaoList3); - actionsDaoList.addAll(actionsDaoList4); - repository - .saveAll(actionsDaoList) - .blockLast(); - - ActionsListResponse response = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions/test-user") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(response.getTotalCount()).isEqualTo(10); - assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); - } - - @Test - void thatActionsCanBeGottenForUserWithShowLastHoursWithEmptyList() { - // First mixed user actions for different users - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - actionsDaoList.addAll(actionsDaoList2); - actionsDaoList.addAll(actionsDaoList3); - actionsDaoList.addAll(actionsDaoList4); - repository - .saveAll(actionsDaoList) - .blockLast(); - - ActionsListResponse response = requestSpecification("test4-user") - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions/test4-user?page=1&pageSize=20&showLastHours=2") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(response.getTotalCount()).isZero(); - } - - @Test - void thatActionsCanBeDeleted(){ - // First mixed user actions for different users - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList2 = ActionFixtures.actionsDaoList(5, "test2-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList3 = ActionFixtures.actionsDaoList(3, "test3-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); - actionsDaoList.addAll(actionsDaoList2); - actionsDaoList.addAll(actionsDaoList3); - repository - .saveAll(actionsDaoList) - .blockLast(); - - requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .delete( "/v1/actions/test-user?deleteAfterHours=2") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - ActionsListResponse responseGetUser = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID2 )) - .when() - .get( "/v1/actions/test-user?page=1&pageSize=20") - .then() - .header("X-Request-Id", X_REQUEST_ID2) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - ActionsListResponse responseGetUser2 = requestSpecification("test2-user") - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID2 )) - .when() - .get( "/v1/actions/test2-user") - .then() - .header("X-Request-Id", X_REQUEST_ID2) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - ActionsListResponse responseGetUser3 = requestSpecification("test3-user") - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID2 )) - .when() - .get( "/v1/actions/test3-user") - .then() - .header("X-Request-Id", X_REQUEST_ID2) - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(responseGetUser.getTotalCount()).isEqualTo(2); - assertThat(responseGetUser2.getTotalCount()).isEqualTo(5); - assertThat(responseGetUser3.getTotalCount()).isEqualTo(3); - } - - @Test - void thatActionsCanNotBeGetForUserBecauseOfWrongUserIdInToken(){ - // First mixed user actions for different users - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - repository - .saveAll(actionsDaoList) - .blockLast(); - - Problem response = requestSpecification("wrong-userId") - .given() - .accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions/test-user") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.BAD_REQUEST.value()) - .extract() - .body() - .as(Problem.class); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()); - } - - @Test - void thatActionsCanNotBeGetForUserBecauseOfWrongHeader(){ - // First mixed user actions for different users - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - repository - .saveAll(actionsDaoList) - .blockLast(); - - Problem response = wrongHeaderRequestSpecification("test-user") - .given() - .accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID )) - .when() - .get( "/v1/actions/test-user") - .then() - .header("X-Request-Id", X_REQUEST_ID) - .statusCode(HttpStatus.BAD_REQUEST.value()) - .extract() - .body() - .as(Problem.class); - - assertThat(response).isNotNull(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()); - } - - @Test - void thatActionsCanBeDeletedForAllUsers(){ - // First mixed user actions for different users - List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(96), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList2 = ActionFixtures.actionsDaoList(8, "test2-user", OffsetDateTime.of(LocalDateTime.now().minusHours(24), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList3 = ActionFixtures.actionsDaoList(5, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - List actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(48), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); - - actionsDaoList.addAll(actionsDaoList2); - actionsDaoList.addAll(actionsDaoList3); - actionsDaoList.addAll(actionsDaoList4); - repository - .saveAll(actionsDaoList) - .blockLast(); - - actionsService.deleteActions(72).block(); - - ActionsListResponse responseGetUser = requestSpecification() - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID2 )) - .when() - .get( "/v1/actions/test-user?page=1&pageSize=20") - .then() - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - ActionsListResponse responseGetUser2 = requestSpecification("test2-user") - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID2 )) - .when() - .get( "/v1/actions/test2-user") - .then() - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - ActionsListResponse responseGetUser3 = requestSpecification("test3-user") - .given() - .accept(MediaType.APPLICATION_JSON_VALUE) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .header(new Header("X-Request-Id", X_REQUEST_ID2 )) - .when() - .get( "/v1/actions/test3-user") - .then() - .statusCode(HttpStatus.OK.value()) - .extract() - .body() - .as(ActionsListResponse.class); - - assertThat(responseGetUser.getTotalCount()).isEqualTo(10); - assertThat(responseGetUser2.getTotalCount()).isEqualTo(8); - assertThat(responseGetUser3.getTotalCount()).isEqualTo(5); - } -} diff --git a/app/src/test/java/org/onap/portalng/history/BaseIntegrationTest.java b/app/src/test/java/org/onap/portalng/history/BaseIntegrationTest.java new file mode 100644 index 0000000..ec682dd --- /dev/null +++ b/app/src/test/java/org/onap/portalng/history/BaseIntegrationTest.java @@ -0,0 +1,181 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.nimbusds.jose.jwk.JWKSet; + +import io.restassured.RestAssured; +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.filter.log.ResponseLoggingFilter; +import io.restassured.specification.RequestSpecification; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.onap.portalng.history.util.IdTokenExchange; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.http.MediaType; + +import java.util.List; +import java.util.UUID; + +/** Base class for all tests that has the common config including port, realm, logging and auth. */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureWireMock(port = 0) +public abstract class BaseIntegrationTest { + +// @TestConfiguration +// public static class Config { +// @Bean +// WireMockConfigurationCustomizer optionsCustomizer() { +// return options -> options.extensions(new ResponseTemplateTransformer(true)); +// } +// } + + @LocalServerPort protected int port; + @Value("${history.realm}") + protected String realm; + + @Value("${history.delete-interval}") + protected String deleteInterval; + + @Autowired protected ObjectMapper objectMapper; + @Autowired private TokenGenerator tokenGenerator; + + @BeforeAll + public static void setup() { + RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter()); + } + + /** Mocks the OIDC auth flow. */ + @BeforeEach + public void mockAuth() { + WireMock.reset(); + + WireMock.stubFor( + WireMock.get( + WireMock.urlMatching( + String.format("/auth/realms/%s/protocol/openid-connect/certs", realm))) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", JWKSet.MIME_TYPE) + .withBody(tokenGenerator.getJwkSet().toString()))); + + final TokenGenerator.TokenGeneratorConfig config = + TokenGenerator.TokenGeneratorConfig.builder().port(port).realm(realm).sub("test-user").build(); + + WireMock.stubFor( + WireMock.post( + WireMock.urlMatching( + String.format("/auth/realms/%s/protocol/openid-connect/token", realm))) + .withBasicAuth("test", "test") + .withRequestBody(WireMock.containing("grant_type=client_credentials")) + .willReturn( + WireMock.aResponse() + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody( + objectMapper + .createObjectNode() + .put("token_type", "bearer") + .put("access_token", tokenGenerator.generateToken(config)) + .put("expires_in", config.getExpireIn().getSeconds()) + .put("refresh_token", tokenGenerator.generateToken(config)) + .put("refresh_expires_in", config.getExpireIn().getSeconds()) + .put("not-before-policy", 0) + .put("session_state", UUID.randomUUID().toString()) + .put("scope", "email profile") + .toString()))); + } + + /** + * Builds an OAuth2 configuration including the roles, port and realm. This config can be used to + * generate OAuth2 access tokens. + * + * @param sub the userId + * @param roles the roles used for RBAC + * @return the OAuth2 configuration + */ + protected TokenGenerator.TokenGeneratorConfig getTokenGeneratorConfig(String sub, List roles) { + return TokenGenerator.TokenGeneratorConfig.builder() + .port(port) + .sub(sub) + .realm(realm) + .roles(roles) + .build(); + } + + /** Get a RequestSpecification that does not have an Identity header. */ + protected RequestSpecification unauthenticatedRequestSpecification() { + return RestAssured.given().port(port); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the onap_admin role to the request. + * @return the definition of the incoming request (northbound) + */ + protected RequestSpecification requestSpecification() { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig("test-user", List.of("foo"))); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header(IdTokenExchange.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the onap_admin role to the request. + * @param userId the userId that should be contained in the incoming request + * @return the definition of the incoming request (northbound) + */ + protected RequestSpecification requestSpecification(String userId) { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(userId, List.of("foo"))); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header(IdTokenExchange.X_AUTH_IDENTITY_HEADER, "Bearer " + idToken); + } + + /** + * Object to store common attributes of requests that are going to be made. Adds an Identity + * header for the onap_admin role to the request. + * @param userId the userId that should be contained in the incoming request + * @return the definition of the incoming request (northbound) + */ + protected RequestSpecification wrongHeaderRequestSpecification(String userId) { + final String idToken = tokenGenerator.generateToken(getTokenGeneratorConfig(userId, List.of("foo"))); + + return unauthenticatedRequestSpecification() + .auth() + .preemptive() + .oauth2(idToken) + .header("X-WRONG-HEADER", "Bearer " + idToken); + } +} diff --git a/app/src/test/java/org/onap/portalng/history/TokenGenerator.java b/app/src/test/java/org/onap/portalng/history/TokenGenerator.java new file mode 100644 index 0000000..1887682 --- /dev/null +++ b/app/src/test/java/org/onap/portalng/history/TokenGenerator.java @@ -0,0 +1,129 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history; + +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@Component +public class TokenGenerator { + + private static final String ROLES_CLAIM = "roles"; + private static final String USERID_CLAIM = "sub"; + + private final Clock clock; + private final RSAKey jwk; + private final JWKSet jwkSet; + private final JWSSigner signer; + + @Autowired + public TokenGenerator(Clock clock) { + try { + this.clock = clock; + jwk = + new RSAKeyGenerator(2048) + .keyUse(KeyUse.SIGNATURE) + .keyID(UUID.randomUUID().toString()) + .generate(); + jwkSet = new JWKSet(jwk); + signer = new RSASSASigner(jwk); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public JWKSet getJwkSet() { + return jwkSet; + } + + public String generateToken(TokenGeneratorConfig config) { + final Instant iat = clock.instant(); + final Instant exp = iat.plus(config.expireIn); + + final JWTClaimsSet claims = + new JWTClaimsSet.Builder() + .jwtID(UUID.randomUUID().toString()) + .subject(UUID.randomUUID().toString()) + .issuer(config.issuer()) + .issueTime(Date.from(iat)) + .expirationTime(Date.from(exp)) + .claim(ROLES_CLAIM, config.getRoles()) + .claim(USERID_CLAIM, config.getSub()) + .build(); + + final SignedJWT jwt = + new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.RS256) + .keyID(jwk.getKeyID()) + .type(JOSEObjectType.JWT) + .build(), + claims); + + try { + jwt.sign(signer); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return jwt.serialize(); + } + + @Getter + @Builder + public static class TokenGeneratorConfig { + private final int port; + + @NonNull private final String sub; + + @NonNull private final String realm; + + @NonNull @Builder.Default private final Duration expireIn = Duration.ofMinutes(5); + + @Builder.Default private final List roles = Collections.emptyList(); + + public String issuer() { + return String.format("http://localhost:%d/auth/realms/%s", port, realm); + } + } +} diff --git a/app/src/test/java/org/onap/portalng/history/actions/ActionDto.java b/app/src/test/java/org/onap/portalng/history/actions/ActionDto.java new file mode 100644 index 0000000..b2b7cd4 --- /dev/null +++ b/app/src/test/java/org/onap/portalng/history/actions/ActionDto.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.actions; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ActionDto { + String type; + String action; + String message; + String downStreamSystem; + String downStreamId; +} diff --git a/app/src/test/java/org/onap/portalng/history/actions/ActionFixtures.java b/app/src/test/java/org/onap/portalng/history/actions/ActionFixtures.java new file mode 100644 index 0000000..4137a58 --- /dev/null +++ b/app/src/test/java/org/onap/portalng/history/actions/ActionFixtures.java @@ -0,0 +1,126 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.actions; + +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.onap.portalng.history.openapi.model.CreateActionRequest; +import org.onap.portalng.history.entities.ActionsDao; + +public class ActionFixtures { + + public static List createActionRequestList( + Integer numberOfActions, String userId, OffsetDateTime createdAt){ + List createActionRequestList = new ArrayList<>(); + for (Integer i = 1; i <= numberOfActions; i++) { + createActionRequestList.add( + generateActionRequest( + "Instantiation", "create", "action" + i, i.toString(), "SO", i, i, i, userId, createdAt)); + } + return createActionRequestList; + } + + public static List createActionRequestListHourOffsetOnly( + Integer numberOfActions, String userId, OffsetDateTime createdAt){ + List createActionRequestList = new ArrayList<>(); + for (Integer i = 1; i <= numberOfActions; i++) { + createActionRequestList.add( + generateActionRequest( + "Instantiation", "create", "action" + i, i.toString(), "SO", i, 0, 0, userId, createdAt)); + } + return createActionRequestList; + } + + public static CreateActionRequest generateActionRequest( + String type, + String action, + String message, + String id, + String downStreamSystem, + Integer deltaHours, + Integer deltaMinutes, + Integer deltaSeconds, + String userId, + OffsetDateTime createdAt) { + ActionDto actionDto = new ActionDto(); + actionDto.setType(type); + actionDto.setAction(action); + actionDto.setMessage(message); + actionDto.setDownStreamSystem(downStreamSystem); + actionDto.setDownStreamId(id); + + return new CreateActionRequest() + .userId(userId) + .action(actionDto) + .actionCreatedAt(createdAt.minusHours(deltaHours).minusMinutes(deltaMinutes).minusSeconds(deltaSeconds)); + } + + public static List actionsDaoList( + Integer numberOfActions, String userId, OffsetDateTime createdAt){ + List actionsDaoList = new ArrayList<>(); + for (Integer i = 1; i <= numberOfActions; i++) { + actionsDaoList.add( + generateActionsDao( + "Instantiation", "create", "action" + i, i.toString(), "SO", i, i, i, userId, createdAt)); + } + return actionsDaoList; + } + + public static ActionsDao generateActionsDao( + String type, + String action, + String message, + String id, + String downStreamSystem, + Integer deltaHours, + Integer deltaMinutes, + Integer deltaSeconds, + String userId, + OffsetDateTime createdAt) { + ActionDto actionDto = new ActionDto(); + actionDto.setType(type); + actionDto.setAction(action); + actionDto.setMessage(message); + actionDto.setDownStreamSystem(downStreamSystem); + actionDto.setDownStreamId(id); + + ActionsDao actionsDao = new ActionsDao(); + actionsDao.setUserId(userId); + actionsDao.setAction(actionDto); + actionsDao.setActionCreatedAt(new Date(createdAt.minusHours(deltaHours).minusMinutes(deltaMinutes).minusSeconds(deltaSeconds).toEpochSecond()*1000)); + return actionsDao; + } + + public static List actionsDaoListHourOffsetOnly( + Integer numberOfActions, String userId, OffsetDateTime createdAt){ + List actionsDaoList = new ArrayList<>(); + for (Integer i = 1; i <= numberOfActions; i++) { + actionsDaoList.add( + generateActionsDao( + "Instantiation", "create", "action" + i, i.toString(), "SO", i, 0, 0, userId, createdAt)); + } + return actionsDaoList; + } +} diff --git a/app/src/test/java/org/onap/portalng/history/actions/ActionsControllerIntegrationTest.java b/app/src/test/java/org/onap/portalng/history/actions/ActionsControllerIntegrationTest.java new file mode 100644 index 0000000..317f582 --- /dev/null +++ b/app/src/test/java/org/onap/portalng/history/actions/ActionsControllerIntegrationTest.java @@ -0,0 +1,571 @@ +/* + * + * Copyright (c) 2022. Deutsche Telekom AG + * + * 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 + * + * + */ + +package org.onap.portalng.history.actions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onap.portalng.history.BaseIntegrationTest; +import org.onap.portalng.history.openapi.model.ActionResponse; +import org.onap.portalng.history.openapi.model.ActionsListResponse; +import org.onap.portalng.history.openapi.model.CreateActionRequest; +import org.onap.portalng.history.openapi.model.Problem; +import org.onap.portalng.history.entities.ActionsDao; +import org.onap.portalng.history.repository.ActionsRepository; +import org.onap.portalng.history.services.ActionsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import io.restassured.http.Header; + +class ActionsControllerIntegrationTest extends BaseIntegrationTest { + + protected static final String X_REQUEST_ID = "addf6005-3075-4c80-b7bc-2c70b7d42b57"; + protected static final String X_REQUEST_ID2 = "addf6005-3075-4c80-b7bc-2c70b7d42b22"; + + @Autowired + ActionsService actionsService; + + @Autowired + private ActionsRepository repository; + + // @Value("${history.save-interval}") + protected Integer saveInterval = 72; + + @BeforeEach + void deleteMongoDataBase(){ + repository.deleteAll().block(); + } + + @Test + void thatUserCanHaveNoHistoryYet() throws JsonProcessingException { + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertNotNull(response); + assertThat(response.getTotalCount()).isEqualTo(0); + } + + @Test + void thatActionCanBeSaved() throws Exception{ + ActionDto actionDto = new ActionDto(); + actionDto.setType("instantiation"); + actionDto.setAction("create"); + actionDto.setDownStreamId("1234"); + actionDto.setDownStreamSystem("SO"); + actionDto.setMessage("no details"); + + CreateActionRequest actionRequest = new CreateActionRequest() + .actionCreatedAt(OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)) + .userId("test-user") + .action(actionDto); + + ActionResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .body(actionRequest) + .when() + .post( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionResponse.class); + + assertThat(response.getActionCreatedAt()).isEqualTo(actionRequest.getActionCreatedAt().truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_DATE_TIME)); + assertThat(response.getSaveInterval()).isEqualTo(saveInterval); + assertThat(objectMapper.writeValueAsString(response.getAction())).isEqualTo(objectMapper.writeValueAsString(actionRequest.getAction())); + } + + @Test + void thatActionsCanBeListedWithoutParameter() throws JsonProcessingException { + List actionsDaoList = ActionFixtures.actionsDaoList(500, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(10); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + assertThat(response.getActionsList().get(9).getSaveInterval()).isEqualTo(saveInterval); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(9).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(9).getAction())); + } + + @Test + void thatActionsCanBeListedWithParameter() throws JsonProcessingException { + List actionsDaoList = ActionFixtures.actionsDaoList(20, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions?page=1&pageSize=5") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(5); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + assertThat(response.getActionsList().get(4).getSaveInterval()).isEqualTo(saveInterval); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(4).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(4).getAction())); + } + + @Test + void thatActionsCanBeListedWithParameterInOrderByActionCreatedAt() { + List actionsDaoList = ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusDays(2), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); + actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(6), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); + actionsDaoList.addAll(ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(12), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS))); + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions?page=1&pageSize=5") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(5); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + assertThat(response.getActionsList().get(4).getSaveInterval()).isEqualTo(saveInterval); + assertThat(response.getActionsList().get(0).getActionCreatedAt()).isEqualTo(actionsDaoList.get(5).getActionCreatedAt().toInstant().atOffset(ZoneOffset.UTC)); + assertThat(response.getActionsList().get(4).getActionCreatedAt()).isEqualTo(actionsDaoList.get(9).getActionCreatedAt().toInstant().atOffset(ZoneOffset.UTC)); + } + + @Test + void thatActionsCanBeListedWithShowLastHours() throws JsonProcessingException { + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(20, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions?page=1&pageSize=20&showLastHours=12") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(12); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(0).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(0).getAction())); + assertThat(objectMapper.writeValueAsString(response.getActionsList().get(11).getAction())).isEqualTo(objectMapper.writeValueAsString(actionsDaoList.get(11).getAction())); + } + + @Test + void thatActionsCanNotBeListedWithWrongPageParameter() { + List actionsDaoList = ActionFixtures.actionsDaoList(5, "test-user", OffsetDateTime.of(LocalDateTime.now().minusDays(2), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + + Problem response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions?page=0&pageSize=5") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract() + .body() + .as(Problem.class); + + assertThat(response.getStatus()).isEqualTo(500); + } + + @Test + void thatActionsCanBeGetForUserWithShowLastHours(){ + // First mixed user actions for different users + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user?page=1&pageSize=20&showLastHours=2") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(2); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + } + + @Test + void thatActionsCanBeGottenForUserWithShowLastHoursWithMinusValue(){ + // First mixed user actions for different users + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().plusHours(48), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + actionsDaoList.addAll(actionsDaoList4); + + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user?page=1&pageSize=20&showLastHours=-2") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(10); + } + + @Test + void thatActionsCanBeGottenForUserWithoutParameter(){ + // First mixed user actions for different users + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + actionsDaoList.addAll(actionsDaoList4); + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isEqualTo(10); + assertThat(response.getActionsList().get(0).getSaveInterval()).isEqualTo(saveInterval); + } + + @Test + void thatActionsCanBeGottenForUserWithShowLastHoursWithEmptyList() { + // First mixed user actions for different users + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList2 = ActionFixtures.actionsDaoList(10, "test2-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList3 = ActionFixtures.actionsDaoList(10, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + actionsDaoList.addAll(actionsDaoList4); + repository + .saveAll(actionsDaoList) + .blockLast(); + + ActionsListResponse response = requestSpecification("test4-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test4-user?page=1&pageSize=20&showLastHours=2") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(response.getTotalCount()).isZero(); + } + + @Test + void thatActionsCanBeDeleted(){ + // First mixed user actions for different users + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.now().plusMinutes(30).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList2 = ActionFixtures.actionsDaoList(5, "test2-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList3 = ActionFixtures.actionsDaoList(3, "test3-user", OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)); + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + repository + .saveAll(actionsDaoList) + .blockLast(); + + requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .delete( "/v1/actions/test-user?deleteAfterHours=2") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test-user?page=1&pageSize=20") + .then() + .header("X-Request-Id", X_REQUEST_ID2) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser2 = requestSpecification("test2-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test2-user") + .then() + .header("X-Request-Id", X_REQUEST_ID2) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser3 = requestSpecification("test3-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test3-user") + .then() + .header("X-Request-Id", X_REQUEST_ID2) + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(responseGetUser.getTotalCount()).isEqualTo(2); + assertThat(responseGetUser2.getTotalCount()).isEqualTo(5); + assertThat(responseGetUser3.getTotalCount()).isEqualTo(3); + } + + @Test + void thatActionsCanNotBeGetForUserBecauseOfWrongUserIdInToken(){ + // First mixed user actions for different users + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + + Problem response = requestSpecification("wrong-userId") + .given() + .accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract() + .body() + .as(Problem.class); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()); + } + + @Test + void thatActionsCanNotBeGetForUserBecauseOfWrongHeader(){ + // First mixed user actions for different users + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + repository + .saveAll(actionsDaoList) + .blockLast(); + + Problem response = wrongHeaderRequestSpecification("test-user") + .given() + .accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID )) + .when() + .get( "/v1/actions/test-user") + .then() + .header("X-Request-Id", X_REQUEST_ID) + .statusCode(HttpStatus.BAD_REQUEST.value()) + .extract() + .body() + .as(Problem.class); + + assertThat(response).isNotNull(); + assertThat(response.getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value()); + } + + @Test + void thatActionsCanBeDeletedForAllUsers(){ + // First mixed user actions for different users + List actionsDaoList = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(96), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList2 = ActionFixtures.actionsDaoList(8, "test2-user", OffsetDateTime.of(LocalDateTime.now().minusHours(24), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList3 = ActionFixtures.actionsDaoList(5, "test3-user", OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + List actionsDaoList4 = ActionFixtures.actionsDaoListHourOffsetOnly(10, "test-user", OffsetDateTime.of(LocalDateTime.now().minusHours(48), ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); + + actionsDaoList.addAll(actionsDaoList2); + actionsDaoList.addAll(actionsDaoList3); + actionsDaoList.addAll(actionsDaoList4); + repository + .saveAll(actionsDaoList) + .blockLast(); + + actionsService.deleteActions(72).block(); + + ActionsListResponse responseGetUser = requestSpecification() + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test-user?page=1&pageSize=20") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser2 = requestSpecification("test2-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test2-user") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + ActionsListResponse responseGetUser3 = requestSpecification("test3-user") + .given() + .accept(MediaType.APPLICATION_JSON_VALUE) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header(new Header("X-Request-Id", X_REQUEST_ID2 )) + .when() + .get( "/v1/actions/test3-user") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .body() + .as(ActionsListResponse.class); + + assertThat(responseGetUser.getTotalCount()).isEqualTo(10); + assertThat(responseGetUser2.getTotalCount()).isEqualTo(8); + assertThat(responseGetUser3.getTotalCount()).isEqualTo(5); + } +} diff --git a/buildSrc/src/main/groovy/org.onap.portal.history.java-application-conventions.gradle b/buildSrc/src/main/groovy/org.onap.portal.history.java-application-conventions.gradle deleted file mode 100644 index be78cf2..0000000 --- a/buildSrc/src/main/groovy/org.onap.portal.history.java-application-conventions.gradle +++ /dev/null @@ -1,11 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - */ - -plugins { - // Apply the common convention plugin for shared build configuration between library and application projects. - id 'org.onap.portal.history.java-common-conventions' - - // Apply the application plugin to add support for building a CLI application in Java. - id 'application' -} diff --git a/buildSrc/src/main/groovy/org.onap.portal.history.java-common-conventions.gradle b/buildSrc/src/main/groovy/org.onap.portal.history.java-common-conventions.gradle deleted file mode 100644 index ee5af57..0000000 --- a/buildSrc/src/main/groovy/org.onap.portal.history.java-common-conventions.gradle +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - */ - -plugins { - // Apply the java Plugin to add support for Java. - id 'java' - id 'idea' -} - -repositories { -maven { - url "https://plugins.gradle.org/m2/" - } - mavenCentral() -} - -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.2' - - // Use JUnit Jupiter for testing. - testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' -} - -tasks.named('test') { - // Use JUnit Platform for unit tests. - useJUnitPlatform() -} diff --git a/buildSrc/src/main/groovy/org.onap.portal.history.java-library-conventions.gradle b/buildSrc/src/main/groovy/org.onap.portal.history.java-library-conventions.gradle deleted file mode 100644 index 24ddad6..0000000 --- a/buildSrc/src/main/groovy/org.onap.portal.history.java-library-conventions.gradle +++ /dev/null @@ -1,11 +0,0 @@ -/* - * This file was generated by the Gradle 'init' task. - */ - -plugins { - // Apply the common convention plugin for shared build configuration between library and application projects. - id 'org.onap.portal.history.java-common-conventions' - - // Apply the java-library plugin for API and implementation separation. - id 'java-library' -} diff --git a/buildSrc/src/main/groovy/org.onap.portalng.history.java-application-conventions.gradle b/buildSrc/src/main/groovy/org.onap.portalng.history.java-application-conventions.gradle new file mode 100644 index 0000000..59fbf68 --- /dev/null +++ b/buildSrc/src/main/groovy/org.onap.portalng.history.java-application-conventions.gradle @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Apply the common convention plugin for shared build configuration between library and application projects. + id 'org.onap.portalng.history.java-common-conventions' + + // Apply the application plugin to add support for building a CLI application in Java. + id 'application' +} diff --git a/buildSrc/src/main/groovy/org.onap.portalng.history.java-common-conventions.gradle b/buildSrc/src/main/groovy/org.onap.portalng.history.java-common-conventions.gradle new file mode 100644 index 0000000..ee5af57 --- /dev/null +++ b/buildSrc/src/main/groovy/org.onap.portalng.history.java-common-conventions.gradle @@ -0,0 +1,28 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Apply the java Plugin to add support for Java. + id 'java' + id 'idea' +} + +repositories { +maven { + url "https://plugins.gradle.org/m2/" + } + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.2' + + // Use JUnit Jupiter for testing. + testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/buildSrc/src/main/groovy/org.onap.portalng.history.java-library-conventions.gradle b/buildSrc/src/main/groovy/org.onap.portalng.history.java-library-conventions.gradle new file mode 100644 index 0000000..0d6eaf4 --- /dev/null +++ b/buildSrc/src/main/groovy/org.onap.portalng.history.java-library-conventions.gradle @@ -0,0 +1,11 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + // Apply the common convention plugin for shared build configuration between library and application projects. + id 'org.onap.portalng.history.java-common-conventions' + + // Apply the java-library plugin for API and implementation separation. + id 'java-library' +} diff --git a/openapi/build.gradle b/openapi/build.gradle index 7ec2657..47c8f75 100644 --- a/openapi/build.gradle +++ b/openapi/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.onap.portal.history.java-application-conventions' + id 'org.onap.portalng.history.java-application-conventions' id 'org.openapi.generator' } @@ -8,9 +8,9 @@ ext { } dependencies { - implementation "org.openapitools:openapi-generator:$openapiVersion" + compileOnly "org.openapitools:openapi-generator:$openapiVersion" constraints { - implementation('jakarta.validation:jakarta.validation-api:3.0.2') { + compileOnly('jakarta.validation:jakarta.validation-api:3.0.2') { because 'because min max validation wouldnt work with older versions of dependency' } } @@ -37,9 +37,9 @@ openApiGenerate { generateApiDocumentation = false generateModelTests = false generateModelDocumentation = false - invokerPackage = "org.onap.portal.history.openapi" - apiPackage = "org.onap.portal.history.openapi.api" - modelPackage = "org.onap.portal.history.openapi.model" + invokerPackage = "org.onap.portalng.history.openapi" + apiPackage = "org.onap.portalng.history.openapi.api" + modelPackage = "org.onap.portalng.history.openapi.model" } compileJava { -- cgit 1.2.3-korg