Sync Integ to Master
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / impl / CsarValidationUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.sdc.be.components.impl;
22
23 import fj.data.Either;
24 import org.apache.commons.lang3.tuple.ImmutablePair;
25 import org.openecomp.sdc.be.config.BeEcompErrorManager;
26 import org.openecomp.sdc.be.config.BeEcompErrorManager.ErrorSeverity;
27 import org.openecomp.sdc.be.dao.api.ActionStatus;
28 import org.openecomp.sdc.be.impl.ComponentsUtils;
29 import org.openecomp.sdc.be.tosca.CsarUtils;
30 import org.openecomp.sdc.common.util.GeneralUtility;
31 import org.openecomp.sdc.exception.ResponseFormat;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import java.io.ByteArrayInputStream;
36 import java.io.IOException;
37 import java.io.StringReader;
38 import java.util.*;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41 import java.util.stream.Collectors;
42
43 public class CsarValidationUtils {
44
45     private static final Logger log = LoggerFactory.getLogger(CsarValidationUtils.class);
46
47     private static final String TOSCA_META_FILE_VERSION = "TOSCA-Meta-File-Version";
48
49     private static final String CSAR_VERSION = "CSAR-Version";
50
51     private static final String CREATED_BY = "Created-By";
52
53     private static final String NEW_LINE_DELM = "\n";
54
55     public final static String TOSCA_METADATA = "TOSCA-Metadata";
56     public final static String TOSCA_FILE = "TOSCA.meta";
57     public final static String DEL_PATTERN = "([/\\\\]+)";
58     public static final String TOSCA_METADATA_PATH_PATTERN = TOSCA_METADATA +
59             // Artifact Group (i.e Deployment/Informational)
60             DEL_PATTERN + TOSCA_FILE;
61
62     public static final String TOSCA_META_ENTRY_DEFINITIONS = "Entry-Definitions";
63
64     private static final String[] TOSCA_METADATA_FIELDS = { TOSCA_META_FILE_VERSION, CSAR_VERSION, CREATED_BY, TOSCA_META_ENTRY_DEFINITIONS };
65
66     public final static String ARTIFACTS_METADATA_FILE = "HEAT.meta";
67
68     public static final String TOSCA_CSAR_EXTENSION = ".csar";
69 /**
70  * Validates Csar
71  * @param csar
72  * @param csarUUID
73  * @param componentsUtils
74  * @return
75  */
76     public static Either<Boolean, ResponseFormat> validateCsar(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
77         Either<Boolean, ResponseFormat> validateStatus = validateIsTOSCAMetadataExist(csar, csarUUID, componentsUtils);
78         if (validateStatus.isRight()) {
79             return Either.right(validateStatus.right().value());
80         }
81
82         removeNonUniqueArtifactsFromCsar(csar);
83
84         log.trace("TOSCA-Metadata/TOSCA.meta file found, CSAR id {}", csarUUID);
85         validateStatus = validateTOSCAMetadataFile(csar, csarUUID, componentsUtils);
86         if (validateStatus.isRight()) {
87             return Either.right(validateStatus.right().value());
88         }
89         return Either.left(true);
90     }
91
92     private static void removeNonUniqueArtifactsFromCsar(Map<String, byte[]> csar) {
93
94         List<String> nonUniqueArtifactsToRemove = new ArrayList<>();
95         String[] paths = csar.keySet().toArray(new String[csar.keySet().size()]);
96         int numberOfArtifacts = paths.length;
97         for(int i = 0; i < numberOfArtifacts; ++i ){
98             collectNonUniqueArtifact(paths, i, numberOfArtifacts, nonUniqueArtifactsToRemove);
99         }
100         nonUniqueArtifactsToRemove.stream().forEach(path->csar.remove(path));
101     }
102
103     private static void collectNonUniqueArtifact( String[] paths, int currInd, int numberOfArtifacts, List<String> nonUniqueArtifactsToRemove) {
104
105         String[] parsedPath = paths[currInd].split("/");
106         String[] otherParsedPath;
107         int artifactNameInd = parsedPath.length - 1;
108         for(int j = currInd + 1; j < numberOfArtifacts; ++j ){
109             otherParsedPath = paths[j].split("/");
110             if(parsedPath.length == otherParsedPath.length && parsedPath.length > 3 && isEqualArtifactNames(parsedPath, otherParsedPath)){
111                 log.error("Can't upload two artifact with the same name {}. The artifact with path {} will be handled, and the artifact with path {} will be ignored. ",
112                         parsedPath[artifactNameInd], paths[currInd], paths[j]);
113                 nonUniqueArtifactsToRemove.add(paths[j]);
114             }
115         }
116     }
117
118     private static boolean isEqualArtifactNames(String[] parsedPath, String[] otherParsedPath) {
119         boolean isEqualArtifactNames = false;
120         int artifactNameInd = parsedPath.length - 1;
121         int artifactGroupTypeInd = parsedPath.length - 3;
122         String groupType = parsedPath[artifactGroupTypeInd];
123         String artifactName = parsedPath[artifactNameInd];
124         String otherGroupType = otherParsedPath[artifactGroupTypeInd];
125         String otherArtifactName = otherParsedPath[artifactNameInd];
126         String vfcToscaName = parsedPath.length == 5 ? parsedPath[1] : null;
127
128         if(artifactName.equalsIgnoreCase(otherArtifactName) && groupType.equalsIgnoreCase(otherGroupType)){
129             isEqualArtifactNames = vfcToscaName == null ? true : vfcToscaName.equalsIgnoreCase(otherParsedPath[1]);
130         }
131         return isEqualArtifactNames;
132     }
133
134     public static Either<ImmutablePair<String, String>, ResponseFormat> getToscaYaml(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
135         Either<Boolean, ResponseFormat> validateStatus = validateIsTOSCAMetadataExist(csar, csarUUID, componentsUtils);
136         if (validateStatus.isRight()) {
137             return Either.right(validateStatus.right().value());
138         }
139         Pattern pattern = Pattern.compile(TOSCA_METADATA_PATH_PATTERN);
140         Optional<String> keyOp = csar.keySet().stream().filter(k -> pattern.matcher(k).matches()).findAny();
141         if(!keyOp.isPresent()){
142             log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID);
143             BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
144             return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
145         }
146         byte[] toscaMetaBytes = csar.get(keyOp.get());
147         Properties props = new Properties();
148         try {
149             String propStr = new String(toscaMetaBytes);
150             props.load(new StringReader(propStr.replace("\\","\\\\")));
151         } catch (IOException e) {
152             log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID, e);
153             BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
154             return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
155         }
156
157         String yamlFileName = props.getProperty(TOSCA_META_ENTRY_DEFINITIONS);
158         String[] ops = yamlFileName.split(DEL_PATTERN);
159         List<String> list = Arrays.asList(ops);
160         String result = list.stream().map(x -> x).collect(Collectors.joining(DEL_PATTERN));
161         keyOp = csar.keySet().stream().filter(k -> Pattern.compile(result).matcher(k).matches()).findAny();
162         if(!keyOp.isPresent()){
163             log.debug("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file, csar ID {}", csarUUID);
164             BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
165             return Either.right(componentsUtils.getResponseFormat(ActionStatus.YAML_NOT_FOUND_IN_CSAR, csarUUID, yamlFileName));
166         }
167
168         log.trace("Found Entry-Definitions property in TOSCA-Metadata/TOSCA.meta, Entry-Definitions: {}, CSAR id: {}", yamlFileName, csarUUID);
169         byte[] yamlFileBytes = csar.get(yamlFileName);
170         if (yamlFileBytes == null) {
171             log.debug("Entry-Definitions {} file not found in csar, csar ID {}", yamlFileName, csarUUID);
172             BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions " + yamlFileName + " file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
173             return Either.right(componentsUtils.getResponseFormat(ActionStatus.YAML_NOT_FOUND_IN_CSAR, csarUUID, yamlFileName));
174         }
175
176         String yamlFileContents = new String(yamlFileBytes);
177
178         return Either.left(new ImmutablePair<String, String>(yamlFileName, yamlFileContents));
179     }
180
181     public static Either<ImmutablePair<String, String>, ResponseFormat> getArtifactsMeta(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
182
183         if( !csar.containsKey(CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE) ) {
184             log.debug("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file, csar ID {}", csarUUID);
185             BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
186             return Either.right(componentsUtils.getResponseFormat(ActionStatus.YAML_NOT_FOUND_IN_CSAR, csarUUID, ARTIFACTS_METADATA_FILE));
187         }
188
189         log.trace("Found Entry-Definitions property in TOSCA-Metadata/TOSCA.meta, Entry-Definitions: {}, CSAR id: {}", ARTIFACTS_METADATA_FILE, csarUUID);
190         byte[] artifactsMetaBytes = csar.get(CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE);
191         if (artifactsMetaBytes == null) {
192             log.debug("Entry-Definitions {}{} file not found in csar, csar ID {}", CsarUtils.ARTIFACTS_PATH, ARTIFACTS_METADATA_FILE, csarUUID);
193             BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions " + CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE + " file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
194             return Either.right(componentsUtils.getResponseFormat(ActionStatus.YAML_NOT_FOUND_IN_CSAR, csarUUID, CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE));
195         }
196
197         String artifactsFileContents = new String(artifactsMetaBytes);
198
199         return Either.left(new ImmutablePair<String, String>(CsarUtils.ARTIFACTS_PATH + ARTIFACTS_METADATA_FILE, artifactsFileContents));
200     }
201
202     public static Either<ImmutablePair<String, byte[]>, ResponseFormat> getArtifactsContent(String csarUUID, Map<String, byte[]> csar, String artifactPath, String artifactName, ComponentsUtils componentsUtils) {
203         if (!csar.containsKey(artifactPath)) {
204             log.debug("Entry-Definitions entry not found in Artifacts/HEAT.meta file, csar ID {}", csarUUID);
205             BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions entry not found in TOSCA-Metadata/TOSCA.meta file in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
206             return Either.right(componentsUtils.getResponseFormat(ActionStatus.ARTIFACT_NOT_FOUND_IN_CSAR, CsarUtils.ARTIFACTS_PATH + artifactName, csarUUID));
207         }
208
209         log.trace("Found Entry-Definitions property in Artifacts/HEAT.meta, Entry-Definitions: {}, CSAR id: {}", artifactPath, csarUUID);
210         byte[] artifactFileBytes = csar.get(artifactPath);
211         if (artifactFileBytes == null) {
212             log.debug("Entry-Definitions {}{} file not found in csar, csar ID {}", CsarUtils.ARTIFACTS_PATH, artifactName, csarUUID);
213             BeEcompErrorManager.getInstance().logInternalDataError("Entry-Definitions " + artifactPath + " file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
214             return Either.right(componentsUtils.getResponseFormat(ActionStatus.ARTIFACT_NOT_FOUND_IN_CSAR, artifactPath, csarUUID));
215         }
216
217         return Either.left(new ImmutablePair<String, byte[]>(artifactName, artifactFileBytes));
218     }
219
220     private static Either<Boolean, ResponseFormat> validateTOSCAMetadataFile(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
221
222         Pattern pattern = Pattern.compile(TOSCA_METADATA_PATH_PATTERN);
223         Optional<String> keyOp = csar.keySet().stream().filter(k -> pattern.matcher(k).matches()).findAny();
224         if(!keyOp.isPresent()){
225             log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID);
226             BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
227             return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
228         }
229
230         byte[] toscaMetaBytes = csar.get(keyOp.get());
231         String toscaMetadata = new String(toscaMetaBytes);
232         String[] splited = toscaMetadata.split(NEW_LINE_DELM);
233         if (splited == null || splited.length < TOSCA_METADATA_FIELDS.length) {
234             log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID);
235             BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
236             return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
237         }
238
239         Either<Boolean, ResponseFormat> block_0Status = validateBlock_0(csarUUID, splited, componentsUtils);
240         if (block_0Status.isRight()) {
241             return Either.right(block_0Status.right().value());
242         }
243
244         return Either.left(true);
245
246     }
247
248     private static Either<Boolean, ResponseFormat> validateBlock_0(String csarUUID, String[] splited, ComponentsUtils componentsUtils) {
249         int index = 0;
250         for (String toscaField : TOSCA_METADATA_FIELDS) {
251
252             Properties props = new Properties();
253
254             try {
255                 props.load(new ByteArrayInputStream(splited[index].getBytes()));
256             } catch (IOException e) {
257                 log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID, e);
258                 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
259                 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
260             }
261             if (!props.containsKey(toscaField)) {
262                 log.debug("TOSCA.meta file format is invalid: No new line after block_0 as expected in csar, csar ID {}", csarUUID);
263                 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
264                 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
265             }
266             String value = props.getProperty(toscaField);
267             if (value == null || value.isEmpty()) {
268                 log.debug("TOSCA-Metadata/TOSCA.meta file is not in expected key-value form in csar, csar ID {}", csarUUID);
269                 BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
270                 return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
271             }
272
273             // TOSCA-Meta-File-Version & CSAR-Version : digit.digit - format
274             // validation
275             if (toscaField.equals(TOSCA_META_FILE_VERSION) || toscaField.equals(CSAR_VERSION)) {
276                 if (!validateTOSCAMetaProperty(value)) {
277                     log.debug("TOSCA-Metadata/TOSCA.meta file contains {} in wrong format (digit.digit), csar ID {}", toscaField, csarUUID);
278                     BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not in expected key-value form in CSAR with id " + csarUUID, "CSAR internals are invalid", ErrorSeverity.ERROR);
279                     return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID_FORMAT, csarUUID));
280                 }
281             }
282             index++;
283         }
284         return Either.left(true);
285     }
286
287     private static boolean validateTOSCAMetaProperty(String toscaProperty) {
288         final String FLOAT_STRING = "^\\d{1}[.]\\d{1}$";
289         final Pattern FLOAT_PATTERN = Pattern.compile(FLOAT_STRING);
290
291         Matcher floatMatcher = FLOAT_PATTERN.matcher(toscaProperty);
292         return floatMatcher.matches();
293     }
294
295     private static Either<Boolean, ResponseFormat> validateIsTOSCAMetadataExist(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
296         if (csar == null || csar.isEmpty()) {
297             log.debug("Error when fetching csar with ID {}", csarUUID);
298             BeEcompErrorManager.getInstance().logBeDaoSystemError("Creating resource from CSAR: fetching CSAR with id " + csarUUID + " failed");
299             ResponseFormat responseFormat = componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID, csarUUID);
300             return Either.right(responseFormat);
301         }
302
303         Pattern pattern = Pattern.compile(TOSCA_METADATA_PATH_PATTERN);
304         Optional<String> keyOp = csar.keySet().stream().filter(k -> pattern.matcher(k).matches()).findAny();
305         if(!keyOp.isPresent()){
306
307             log.debug("TOSCA-Metadata/TOSCA.meta file not found in csar, csar ID {}", csarUUID);
308             BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
309             return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID, csarUUID));
310         }
311         byte[] toscaMetaBytes = csar.get(keyOp.get());
312         // && exchanged for ||
313         if (toscaMetaBytes == null || toscaMetaBytes.length == 0) {
314             log.debug("TOSCA-Metadata/TOSCA.meta file not found in csar, csar ID {}", csarUUID);
315             BeEcompErrorManager.getInstance().logInternalDataError("TOSCA-Metadata/TOSCA.meta file not found in CSAR with id " + csarUUID, "CSAR structure is invalid", ErrorSeverity.ERROR);
316             return Either.right(componentsUtils.getResponseFormat(ActionStatus.CSAR_INVALID, csarUUID));
317         }
318
319         return Either.left(Boolean.TRUE);
320     }
321
322     public static Either<String, ResponseFormat> getToscaYamlChecksum(Map<String, byte[]> csar, String csarUUID, ComponentsUtils componentsUtils) {
323
324         Either<ImmutablePair<String, String>, ResponseFormat> toscaYamlRes = getToscaYaml(csar, csarUUID, componentsUtils);
325         if (toscaYamlRes.isRight() || toscaYamlRes.left().value() == null || toscaYamlRes.left().value().getRight() == null) {
326             log.debug("Faild to create toscaYamlChecksum for csar, csar ID {}", csarUUID);
327             return Either.right(toscaYamlRes.right().value());
328         }
329
330         String newCheckSum = GeneralUtility.calculateMD5Base64EncodedByByteArray(toscaYamlRes.left().value().getRight().getBytes());
331         return Either.left(newCheckSum);
332
333     }
334
335     public static boolean isCsarPayloadName(String payloadName) {
336         if (payloadName == null)
337             return false;
338         return payloadName.toLowerCase().endsWith(TOSCA_CSAR_EXTENSION);
339     }
340
341 }