Error when template-prefix-names list is set as an input
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / inbounds / selfservice-api / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / selfservice / api / KafkaPublishAuditService.kt
1 /*
2  * Copyright © 2020 Bell Canada
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api
18
19 import com.fasterxml.jackson.databind.JsonNode
20 import com.fasterxml.jackson.databind.node.ObjectNode
21 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput
22 import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutput
23 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
24 import org.onap.ccsdk.cds.blueprintsprocessor.message.service.BluePrintMessageLibPropertyService
25 import org.onap.ccsdk.cds.blueprintsprocessor.message.service.BlueprintMessageProducerService
26 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
27 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
28 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive
29 import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants
30 import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintCatalogService
31 import org.onap.ccsdk.cds.controllerblueprints.core.service.PropertyAssignmentService
32 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils
33 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
34 import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils
35 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
36 import org.slf4j.LoggerFactory
37 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
38 import org.springframework.stereotype.Service
39 import javax.annotation.PostConstruct
40
41 /**
42  * Audit service used to produce execution service input and output message
43  * sent into dedicated kafka topics.
44  */
45 @ConditionalOnProperty(
46         name = ["blueprintsprocessor.messageproducer.self-service-api.audit.kafkaEnable"],
47         havingValue = "true"
48 )
49 @Service
50 class KafkaPublishAuditService(
51     private val bluePrintMessageLibPropertyService: BluePrintMessageLibPropertyService,
52     private val blueprintsProcessorCatalogService: BluePrintCatalogService
53 ) : PublishAuditService {
54     private var inputInstance: BlueprintMessageProducerService? = null
55     private var outputInstance: BlueprintMessageProducerService? = null
56     private val log = LoggerFactory.getLogger(KafkaPublishAuditService::class.toString())
57
58     companion object {
59         const val INPUT_SELECTOR = "self-service-api.audit.request"
60         const val OUTPUT_SELECTOR = "self-service-api.audit.response"
61     }
62
63     @PostConstruct
64     private fun init() {
65         log.info("Kakfa audit service is enabled")
66     }
67
68     /**
69      * Publish execution input into a kafka topic.
70      * The correlation UUID is used to link the input to its output.
71      * Sensitive data within the request are hidden.
72      */
73     override suspend fun publish(executionServiceInput: ExecutionServiceInput) {
74         val secureExecutionServiceInput = hideSensitiveData(executionServiceInput)
75         this.inputInstance = this.getInputInstance(INPUT_SELECTOR)
76         this.inputInstance!!.sendMessage(secureExecutionServiceInput)
77     }
78
79     /**
80      * Publish execution output into a kafka topic.
81      * The correlation UUID is used to link the output to its input.
82      * A correlation UUID is added to link the input to its output.
83      */
84     override fun publish(correlationUUID: String, executionServiceOutput: ExecutionServiceOutput) {
85         executionServiceOutput.correlationUUID = correlationUUID
86         this.outputInstance = this.getOutputInstance(OUTPUT_SELECTOR)
87         this.outputInstance!!.sendMessage(executionServiceOutput)
88     }
89
90     /**
91      * Return the input kafka producer instance using a selector.
92      */
93     private fun getInputInstance(selector: String): BlueprintMessageProducerService = inputInstance ?: createInstance(selector)
94
95     /**
96      * Return the output kafka producer instance using a selector.
97      */
98     private fun getOutputInstance(selector: String): BlueprintMessageProducerService = outputInstance ?: createInstance(selector)
99
100     /**
101      * Create a kafka producer instance.
102      */
103     private fun createInstance(selector: String): BlueprintMessageProducerService {
104         log.info(
105                 "Setting up message producer($selector)..."
106         )
107         return try {
108             bluePrintMessageLibPropertyService
109                     .blueprintMessageProducerService(selector)
110         } catch (e: Exception) {
111             throw BluePrintProcessorException("failed to create producer service ${e.message}")
112         }
113     }
114
115     /**
116      * Hide sensitive data in the request.
117      * Sensitive data are declared in the resource resolution mapping using
118      * the property metadata "log-protect" set to true.
119      */
120     private suspend fun hideSensitiveData(
121         executionServiceInput: ExecutionServiceInput
122     ): ExecutionServiceInput {
123
124         var clonedExecutionServiceInput = ExecutionServiceInput().apply {
125             correlationUUID = executionServiceInput.correlationUUID
126             commonHeader = executionServiceInput.commonHeader
127             actionIdentifiers = executionServiceInput.actionIdentifiers
128             payload = executionServiceInput.payload.deepCopy()
129             stepData = executionServiceInput.stepData
130         }
131
132         val blueprintName = clonedExecutionServiceInput.actionIdentifiers.blueprintName
133         val workflowName = clonedExecutionServiceInput.actionIdentifiers.actionName
134
135         if (blueprintName == "default") return clonedExecutionServiceInput
136
137         if (clonedExecutionServiceInput.payload
138                         .path("$workflowName-request").has("$workflowName-properties")) {
139
140             /** Retrieving sensitive input parameters */
141             val requestId = clonedExecutionServiceInput.commonHeader.requestId
142             val blueprintVersion = clonedExecutionServiceInput.actionIdentifiers.blueprintVersion
143
144             val basePath = blueprintsProcessorCatalogService.getFromDatabase(blueprintName, blueprintVersion)
145
146             val blueprintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime(requestId, basePath.toString())
147             val blueprintContext = blueprintRuntimeService.bluePrintContext()
148
149             /** Looking for node templates defined as component-resource-resolution */
150             val nodeTemplates = blueprintContext.nodeTemplates()
151             nodeTemplates!!.forEach { nodeTemplate ->
152                 val nodeTemplateName = nodeTemplate.key
153                 val nodeTemplateType = blueprintContext.nodeTemplateByName(nodeTemplateName).type
154                 if (nodeTemplateType == BluePrintConstants.NODE_TEMPLATE_TYPE_COMPONENT_RESOURCE_RESOLUTION) {
155                     val interfaceName = blueprintContext.nodeTemplateFirstInterfaceName(nodeTemplateName)
156                     val operationName = blueprintContext.nodeTemplateFirstInterfaceFirstOperationName(nodeTemplateName)
157
158                     val propertyAssignments: MutableMap<String, JsonNode> =
159                             blueprintContext.nodeTemplateInterfaceOperationInputs(nodeTemplateName, interfaceName, operationName)
160                                     ?: hashMapOf()
161
162                     /** Getting values define in artifact-prefix-names */
163                     val input = executionServiceInput.payload.get("$workflowName-request")
164                     blueprintRuntimeService.assignWorkflowInputs(workflowName, input)
165                     val artifactPrefixNamesNode = propertyAssignments[ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES]
166                     val propertyAssignmentService = PropertyAssignmentService(blueprintRuntimeService)
167                     val artifactPrefixNamesNodeValue = propertyAssignmentService.resolveAssignmentExpression(
168                             BluePrintConstants.MODEL_DEFINITION_TYPE_NODE_TEMPLATE,
169                             nodeTemplateName,
170                             ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES,
171                             artifactPrefixNamesNode!!)
172
173                     val artifactPrefixNames = JacksonUtils.getListFromJsonNode(artifactPrefixNamesNodeValue!!, String::class.java)
174
175                     /** Storing mapping entries with metadata log-protect set to true */
176                     val sensitiveParameters: List<String> = artifactPrefixNames
177                             .map { "$it-mapping" }
178                             .map { blueprintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, it) }
179                             .flatMap { JacksonUtils.getListFromJson(it, ResourceAssignment::class.java) }
180                             .filter { PropertyDefinitionUtils.hasLogProtect(it.property) }
181                             .map { it.name }
182
183                     /** Hiding sensitive input parameters from the request */
184                     var workflowProperties: ObjectNode = clonedExecutionServiceInput.payload
185                             .path("$workflowName-request")
186                             .path("$workflowName-properties") as ObjectNode
187
188                     sensitiveParameters.forEach { sensitiveParameter ->
189                         if (workflowProperties.has(sensitiveParameter)) {
190                             workflowProperties.replace(sensitiveParameter, ApplicationConstants.LOG_REDACTED.asJsonPrimitive())
191                         }
192                     }
193                 }
194             }
195         }
196
197         return clonedExecutionServiceInput
198     }
199 }