97f562979fef0acc742e2c81e717f43934bc0675
[sdc.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019 Nordix Foundation.
4  *  Modification Copyright (C) 2021 Nokia.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.openecomp.sdc.tosca.csar;
23
24 import static org.openecomp.sdc.tosca.csar.CSARConstants.ETSI_VERSION_2_6_1;
25 import static org.openecomp.sdc.tosca.csar.CSARConstants.ETSI_VERSION_2_7_1;
26 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_PNF_METADATA_LIMIT_VERSION_3;
27 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_VNF_METADATA_LIMIT_VERSION_3;
28 import static org.openecomp.sdc.tosca.csar.ManifestTokenType.COMPATIBLE_SPECIFICATION_VERSIONS;
29
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Map.Entry;
34 import java.util.Optional;
35
36 import org.apache.commons.lang.StringUtils;
37 import org.openecomp.sdc.common.errors.Messages;
38
39 import com.vdurmont.semver4j.Semver;
40
41 /**
42  * Processes a SOL004 Manifest.
43  */
44 public class SOL004ManifestOnboarding extends AbstractOnboardingManifest {
45
46     private int maxAllowedMetaEntries;
47
48     @Override
49     protected void processMetadata() {
50         Optional<String> currentLine = getCurrentLine();
51         //SOL004 #4.3.2: The manifest file shall start with the package metadata
52         if (!currentLine.isPresent() || !isMetadata(currentLine.get())) {
53             reportError(Messages.MANIFEST_START_METADATA);
54             continueToProcess = false;
55             return;
56         }
57         while (continueToProcess) {
58             currentLine = readNextNonEmptyLine();
59             if (!currentLine.isPresent()) {
60                 continueToProcess = validateMetadata();
61                 return;
62             }
63             final String metadataLine = currentLine.get();
64             final String metadataEntry = readEntryName(metadataLine).orElse(null);
65             if (!isMetadataEntry(metadataEntry)) {
66                 if (metadata.size() < getMaxAllowedManifestMetaEntries()) {
67                     reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, metadataLine);
68                     continueToProcess = false;
69                     return;
70                 }
71                 continueToProcess = validateMetadata();
72                 return;
73             }
74             final String metadataValue = readEntryValue(metadataLine).orElse(null);
75             addToMetadata(metadataEntry, metadataValue);
76             continueToProcess = isValid();
77         }
78         readNextNonEmptyLine();
79     }
80
81     @Override
82     protected void processBody() {
83         while (continueToProcess) {
84             final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
85             if (manifestTokenType == null) {
86                 getCurrentLine().ifPresent(line -> reportInvalidLine());
87                 break;
88             }
89
90             switch (manifestTokenType) {
91                 case CMS_BEGIN:
92                     readCmsSignature();
93                     break;
94                 case NON_MANO_ARTIFACT_SETS:
95                     processNonManoArtifactEntry();
96                     break;
97                 case SOURCE:
98                     processSource();
99                     break;
100                 default:
101                     getCurrentLine().ifPresent(line -> reportInvalidLine());
102                     continueToProcess = false;
103                     break;
104             }
105         }
106     }
107
108     /**
109      * Processes the {@link ManifestTokenType#NON_MANO_ARTIFACT_SETS} entry.
110      */
111     private void processNonManoArtifactEntry() {
112         Optional<String> currentLine = readNextNonEmptyLine();
113         while (currentLine.isPresent()) {
114             final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
115             if (manifestTokenType == ManifestTokenType.CMS_BEGIN) {
116                 return;
117             }
118             if (manifestTokenType != null) {
119                 reportError(Messages.MANIFEST_INVALID_NON_MANO_KEY, manifestTokenType.getToken());
120                 continueToProcess = false;
121                 return;
122             }
123             final String nonManoKey = readCurrentEntryName().orElse(null);
124             if (nonManoKey == null) {
125                 reportError(Messages.MANIFEST_INVALID_NON_MANO_KEY, currentLine.get());
126                 continueToProcess = false;
127                 return;
128             }
129             readNextNonEmptyLine();
130             final List<String> nonManoSourceList = readNonManoSourceList();
131             if (!isValid()) {
132                 continueToProcess = false;
133                 return;
134             }
135             if (nonManoSourceList.isEmpty()) {
136                 reportError(Messages.MANIFEST_EMPTY_NON_MANO_KEY, nonManoKey);
137                 continueToProcess = false;
138                 return;
139             }
140             if (nonManoSources.get(nonManoKey) == null) {
141                 nonManoSources.put(nonManoKey, nonManoSourceList);
142             } else {
143                 nonManoSources.get(nonManoKey).addAll(nonManoSourceList);
144             }
145             currentLine = getCurrentLine();
146         }
147     }
148
149     /**
150      * Processes {@link ManifestTokenType#SOURCE} entries in {@link ManifestTokenType#NON_MANO_ARTIFACT_SETS}.
151      *
152      * @return A list of sources paths
153      */
154     private List<String> readNonManoSourceList() {
155         final List<String> nonManoSourceList = new ArrayList<>();
156         while (getCurrentLine().isPresent()) {
157             final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
158             if (manifestTokenType != ManifestTokenType.SOURCE) {
159                 break;
160             }
161
162             final String value = readCurrentEntryValue().orElse(null);
163             if (!StringUtils.isEmpty(value)) {
164                 nonManoSourceList.add(value);
165             } else {
166                 reportError(Messages.MANIFEST_EMPTY_NON_MANO_SOURCE);
167                 break;
168             }
169
170             readNextNonEmptyLine();
171         }
172         return nonManoSourceList;
173     }
174
175     /**
176      * Reads a manifest CMS signature.
177      */
178     private void readCmsSignature() {
179         if (cmsSignature != null) {
180             reportError(Messages.MANIFEST_SIGNATURE_DUPLICATED);
181             continueToProcess = false;
182             return;
183         }
184         final StringBuilder cmsSignatureBuilder = new StringBuilder();
185
186         cmsSignatureBuilder.append(currentLine).append("\n");
187         Optional<String> currentLine = readNextNonEmptyLine();
188         if(!getCurrentLine().isPresent()) {
189             return;
190         }
191         while (currentLine.isPresent()) {
192             if (detectLineEntry().orElse(null) == ManifestTokenType.CMS_END) {
193                 cmsSignatureBuilder.append(currentLine.get());
194                 break;
195             }
196             cmsSignatureBuilder.append(currentLine.get()).append("\n");
197             currentLine = readNextNonEmptyLine();
198         }
199
200         if (currentLine.isPresent()) {
201             cmsSignature = cmsSignatureBuilder.toString();
202             readNextNonEmptyLine();
203         }
204
205         if (getCurrentLine().isPresent()) {
206             reportError(Messages.MANIFEST_SIGNATURE_LAST_ENTRY);
207             continueToProcess = false;
208         }
209     }
210
211     /**
212      * Detects the current line manifest token.
213      *
214      * @return the current line manifest token.
215      */
216     private Optional<ManifestTokenType> detectLineEntry() {
217         final Optional<String> currentLine = getCurrentLine();
218         if (currentLine.isPresent()) {
219             final String line = currentLine.get();
220             final String entry = readEntryName(line).orElse(null);
221             if (entry == null) {
222                 return ManifestTokenType.parse(line);
223             } else {
224                 return ManifestTokenType.parse(entry);
225             }
226         }
227         return Optional.empty();
228     }
229
230     /**
231      * Validates the manifest metadata content, reporting errors found.
232      *
233      * @return {@code true} if the metadata content is valid, {@code false} otherwise.
234      */
235     private boolean validateMetadata() {
236         if (metadata.isEmpty()) {
237             reportError(Messages.MANIFEST_NO_METADATA);
238             return false;
239         }
240         String key = metadata.keySet().stream().filter(k -> !COMPATIBLE_SPECIFICATION_VERSIONS.getToken().equals(k))
241                 .findFirst().orElse(null);
242         final ManifestTokenType firstManifestEntryTokenType = ManifestTokenType.parse(key).orElse(null);
243         if (firstManifestEntryTokenType == null) {
244             reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, key);
245             return false;
246         }
247         for (final Entry<String, String> manifestEntry : metadata.entrySet()) {
248             final ManifestTokenType manifestEntryTokenType = ManifestTokenType.parse(manifestEntry.getKey())
249                 .orElse(null);
250             if (manifestEntryTokenType == null) {
251                 reportError(Messages.MANIFEST_METADATA_INVALID_ENTRY1, manifestEntry.getKey());
252                 return false;
253             }
254             if ((firstManifestEntryTokenType.isMetadataVnfEntry() && !manifestEntryTokenType.isMetadataVnfEntry())
255                 || (firstManifestEntryTokenType.isMetadataPnfEntry() && !manifestEntryTokenType.isMetadataPnfEntry())) {
256                 reportError(Messages.MANIFEST_METADATA_UNEXPECTED_ENTRY_TYPE);
257                 return false;
258             }
259         }
260
261         if (metadata.entrySet().size() != getMaxAllowedManifestMetaEntries()) {
262             reportError(Messages.MANIFEST_METADATA_DOES_NOT_MATCH_LIMIT, getMaxAllowedManifestMetaEntries());
263             return false;
264         }
265
266         return true;
267     }
268
269     /**
270      * Processes a Manifest {@link ManifestTokenType#SOURCE} entry.
271      */
272     private void processSource() {
273         final Optional<String> currentLine = getCurrentLine();
274         if (!currentLine.isPresent()) {
275             return;
276         }
277         final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
278         if (manifestTokenType != ManifestTokenType.SOURCE) {
279             return;
280         }
281
282         final String sourceLine = currentLine.get();
283         final String sourcePath = readEntryValue(sourceLine).orElse(null);
284
285         if (sourcePath == null) {
286             reportError(Messages.MANIFEST_EXPECTED_SOURCE_PATH);
287             return;
288         }
289         sources.add(sourcePath);
290         readNextNonEmptyLine();
291         readAlgorithmEntry(sourcePath);
292         readSignatureEntry(sourcePath);
293     }
294
295     /**
296      * Processes entries  {@link ManifestTokenType#ALGORITHM} and {@link ManifestTokenType#HASH} of a {@link
297      * ManifestTokenType#SOURCE} entry.
298      *
299      * @param sourcePath the source path related to the algorithm entry.
300      */
301     private void readAlgorithmEntry(final String sourcePath) {
302         Optional<String> currentLine =  getCurrentLine();
303         if (!currentLine.isPresent()) {
304             return;
305         }
306         final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
307         if (manifestTokenType == ManifestTokenType.HASH) {
308             reportError(Messages.MANIFEST_EXPECTED_ALGORITHM_BEFORE_HASH);
309             continueToProcess = false;
310             return;
311         }
312         if (manifestTokenType != ManifestTokenType.ALGORITHM) {
313             return;
314         }
315         final String algorithmLine = currentLine.get();
316         final String algorithmType = readEntryValue(algorithmLine).orElse(null);
317         if (algorithmType == null) {
318             reportError(Messages.MANIFEST_EXPECTED_ALGORITHM_VALUE);
319             continueToProcess = false;
320             return;
321         }
322
323         currentLine = readNextNonEmptyLine();
324         if (!currentLine.isPresent() || detectLineEntry().orElse(null) != ManifestTokenType.HASH) {
325             reportError(Messages.MANIFEST_EXPECTED_HASH_ENTRY);
326             continueToProcess = false;
327             return;
328         }
329
330         final String hashLine = currentLine.get();
331         final String hash = readEntryValue(hashLine).orElse(null);
332         if (hash == null) {
333             reportError(Messages.MANIFEST_EXPECTED_HASH_VALUE);
334             continueToProcess = false;
335             return;
336         }
337         sourceAndChecksumMap.put(sourcePath, new AlgorithmDigest(algorithmType, hash));
338         readNextNonEmptyLine();
339     }
340
341     /**
342      * Processes entries  {@link ManifestTokenType#SIGNATURE} and {@link ManifestTokenType#CERTIFICATE} of a {@link
343      * ManifestTokenType#SOURCE} entry.
344      *
345      * @param sourcePath the source path related to the algorithm entry.
346      */
347     private void readSignatureEntry(final String sourcePath) {
348         Optional<String> currentLine =  getCurrentLine();
349         if (!currentLine.isPresent()) {
350             return;
351         }
352         final ManifestTokenType manifestTokenType = detectLineEntry().orElse(null);
353         if (manifestTokenType == ManifestTokenType.CERTIFICATE) {
354             reportError(Messages.MANIFEST_EXPECTED_SIGNATURE_BEFORE_CERTIFICATE);
355             continueToProcess = false;
356             return;
357         }
358         if (manifestTokenType != ManifestTokenType.SIGNATURE) {
359             return;
360         }
361         final String signatureLine = currentLine.get();
362         final String signatureFile = readEntryValue(signatureLine).orElse(null);
363         if (signatureFile == null) {
364             reportError(Messages.MANIFEST_EXPECTED_SIGNATURE_VALUE);
365             continueToProcess = false;
366             return;
367         }
368
369         currentLine = readNextNonEmptyLine();
370         if (!currentLine.isPresent() || detectLineEntry().orElse(null) != ManifestTokenType.CERTIFICATE) {
371             sourceAndSignatureMap.put(sourcePath, new SignatureData(signatureFile, null));
372             return;
373         }
374
375         final String certLine = currentLine.get();
376         final String certFile = readEntryValue(certLine).orElse(null);
377         if (certFile == null) {
378             reportError(Messages.MANIFEST_EXPECTED_CERTIFICATE_VALUE);
379             continueToProcess = false;
380             return;
381         }
382         sourceAndSignatureMap.put(sourcePath, new SignatureData(signatureFile, certFile));
383         readNextNonEmptyLine();
384     }
385
386     private int getMaxAllowedManifestMetaEntries() {
387         if (maxAllowedMetaEntries == 0) {
388             boolean isVersion3 = metadata.containsKey(COMPATIBLE_SPECIFICATION_VERSIONS.getToken())
389                 && !getHighestCompatibleVersion().isLowerThan(ETSI_VERSION_2_7_1);
390             //Both PNF and VNF share attribute COMPATIBLE_SPECIFICATION_VERSIONS
391             if (isVersion3)
392                 maxAllowedMetaEntries = metadata.keySet().stream()
393                     .anyMatch(k -> !COMPATIBLE_SPECIFICATION_VERSIONS.getToken().equals(k)
394                             && isMetadataEntry(k) && ManifestTokenType.parse(k).get().isMetadataPnfEntry())
395                     ? MANIFEST_PNF_METADATA_LIMIT_VERSION_3 : MANIFEST_VNF_METADATA_LIMIT_VERSION_3;
396             else
397                 maxAllowedMetaEntries = MAX_ALLOWED_MANIFEST_META_ENTRIES;
398         }
399         return maxAllowedMetaEntries;
400     }
401
402     private Semver getHighestCompatibleVersion() {
403         return Arrays.asList(metadata.get(COMPATIBLE_SPECIFICATION_VERSIONS.getToken()).split(","))
404                 .stream().map(Semver::new).max((v1, v2) -> v1.compareTo(v2))
405                 .orElse(new Semver(ETSI_VERSION_2_6_1));
406     }
407 }