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