f41b44fd79556811cd6dca12f521a795b3753053
[sdc.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019 Nordix Foundation.
4  * ================================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation;
22
23
24 import static org.openecomp.sdc.be.config.NonManoArtifactType.ONAP_PM_DICTIONARY;
25 import static org.openecomp.sdc.be.config.NonManoArtifactType.ONAP_SW_INFORMATION;
26 import static org.openecomp.sdc.be.config.NonManoArtifactType.ONAP_VES_EVENTS;
27 import static org.openecomp.sdc.tosca.csar.CSARConstants.CSAR_VERSION_1_0;
28 import static org.openecomp.sdc.tosca.csar.CSARConstants.CSAR_VERSION_1_1;
29 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_METADATA_LIMIT;
30 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_PNF_METADATA;
31 import static org.openecomp.sdc.tosca.csar.CSARConstants.MANIFEST_VNF_METADATA;
32 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_MANIFEST_FILE_EXT;
33 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_TYPE_PNF;
34 import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_TYPE_VNF;
35 import static org.openecomp.sdc.tosca.csar.ToscaMetaEntry.CREATED_BY_ENTRY;
36 import static org.openecomp.sdc.tosca.csar.ToscaMetaEntry.CSAR_VERSION_ENTRY;
37 import static org.openecomp.sdc.tosca.csar.ToscaMetaEntry.ENTRY_DEFINITIONS;
38 import static org.openecomp.sdc.tosca.csar.ToscaMetaEntry.ETSI_ENTRY_CERTIFICATE;
39 import static org.openecomp.sdc.tosca.csar.ToscaMetaEntry.ETSI_ENTRY_MANIFEST;
40 import static org.openecomp.sdc.tosca.csar.ToscaMetaEntry.TOSCA_META_FILE_VERSION_ENTRY;
41 import static org.openecomp.sdc.tosca.csar.ToscaMetadataFileInfo.TOSCA_META_FILE_VERSION_1_0;
42 import static org.openecomp.sdc.tosca.csar.ToscaMetadataFileInfo.TOSCA_META_PATH_FILE_NAME;
43
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.util.ArrayList;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Optional;
53 import java.util.Set;
54 import java.util.concurrent.CopyOnWriteArrayList;
55 import java.util.stream.Collectors;
56 import org.apache.commons.collections.CollectionUtils;
57 import org.apache.commons.io.FilenameUtils;
58 import org.openecomp.core.impl.ToscaDefinitionImportHandler;
59 import org.openecomp.core.utilities.file.FileContentHandler;
60 import org.openecomp.sdc.be.config.NonManoArtifactType;
61 import org.openecomp.sdc.be.csar.pnf.PnfSoftwareInformation;
62 import org.openecomp.sdc.be.csar.pnf.SoftwareInformationArtifactYamlParser;
63 import org.openecomp.sdc.be.datatypes.enums.ResourceTypeEnum;
64 import org.openecomp.sdc.common.errors.Messages;
65 import org.openecomp.sdc.common.utils.SdcCommon;
66 import org.openecomp.sdc.datatypes.error.ErrorLevel;
67 import org.openecomp.sdc.datatypes.error.ErrorMessage;
68 import org.openecomp.sdc.logging.api.Logger;
69 import org.openecomp.sdc.logging.api.LoggerFactory;
70 import org.openecomp.sdc.tosca.csar.Manifest;
71 import org.openecomp.sdc.tosca.csar.OnboardingToscaMetadata;
72 import org.openecomp.sdc.tosca.csar.SOL004ManifestOnboarding;
73 import org.openecomp.sdc.tosca.csar.ToscaMetaEntry;
74 import org.openecomp.sdc.tosca.csar.ToscaMetadata;
75 import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.OnboardingPackageContentHandler;
76 import org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.csar.validation.exception.MissingCertificateException;
77 import org.openecomp.sdc.vendorsoftwareproduct.impl.orchestration.exceptions.InvalidManifestMetadataException;
78 import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManager;
79 import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManagerException;
80 import org.yaml.snakeyaml.Yaml;
81
82 /**
83  * Validates the contents of the package to ensure it complies with the "CSAR with TOSCA-Metadata directory" structure
84  * as defined in ETSI GS NFV-SOL 004 v2.6.1.
85  */
86 class SOL004MetaDirectoryValidator implements Validator {
87
88     private static final Logger LOGGER = LoggerFactory.getLogger(SOL004MetaDirectoryValidator.class);
89
90     private static final String MANIFEST_SOURCE = "Source";
91     private static final String MANIFEST_NON_MANO_SOURCE = "Non-MANO Source";
92     private final List<ErrorMessage> errorsByFile = new CopyOnWriteArrayList<>();
93     private final SecurityManager securityManager;
94     private OnboardingPackageContentHandler contentHandler;
95     private Set<String> folderList;
96     private ToscaMetadata toscaMetadata;
97
98     public SOL004MetaDirectoryValidator() {
99         securityManager = SecurityManager.getInstance();
100     }
101
102     //for tests purpose
103     SOL004MetaDirectoryValidator(final SecurityManager securityManager) {
104         this.securityManager = securityManager;
105     }
106
107     @Override
108     public Map<String, List<ErrorMessage>> validateContent(final FileContentHandler fileContentHandler) {
109         this.contentHandler = (OnboardingPackageContentHandler) fileContentHandler;
110         this.folderList = contentHandler.getFolderList();
111         parseToscaMetadata();
112         verifyMetadataFile();
113
114         if (packageHasCertificate()) {
115             verifySignedFiles();
116         }
117         return Collections.unmodifiableMap(getAnyValidationErrors());
118     }
119
120     private boolean packageHasCertificate() {
121         final String certificatePath = getCertificatePath().orElse(null);
122         return contentHandler.containsFile(certificatePath);
123     }
124
125     private Optional<String> getCertificatePath() {
126         return toscaMetadata.getEntry(ETSI_ENTRY_CERTIFICATE);
127     }
128
129     /**
130      * Parses the {@link org.openecomp.sdc.tosca.csar.ToscaMetadataFileInfo#TOSCA_META_PATH_FILE_NAME} file
131      */
132     private void parseToscaMetadata() {
133         try {
134             toscaMetadata =
135                 OnboardingToscaMetadata
136                     .parseToscaMetadataFile(contentHandler.getFileContentAsStream(TOSCA_META_PATH_FILE_NAME));
137         } catch (final IOException e) {
138             reportError(ErrorLevel.ERROR, Messages.METADATA_PARSER_INTERNAL.getErrorMessage());
139             LOGGER.error(Messages.METADATA_PARSER_INTERNAL.getErrorMessage(), e.getMessage(), e);
140         }
141     }
142
143     private void verifyMetadataFile() {
144         if (toscaMetadata.isValid() && hasETSIMetadata()) {
145             verifyManifestNameAndExtension();
146             handleMetadataEntries();
147         } else {
148             errorsByFile.addAll(toscaMetadata.getErrors());
149         }
150     }
151
152     private void verifySignedFiles() {
153         final Map<String, String> signedFileMap = contentHandler.getFileAndSignaturePathMap(SecurityManager.ALLOWED_SIGNATURE_EXTENSIONS);
154         final String packageCertificatePath = getCertificatePath().orElse(null);
155         final byte[] packageCert = contentHandler.getFileContent(packageCertificatePath);
156         if(packageCert == null) {
157             throw new MissingCertificateException("Expected package certificate");
158         }
159         signedFileMap.entrySet().stream().filter(entry -> entry.getValue() != null).forEach(entry -> {
160             final String filePath = entry.getKey();
161             final String fileSignaturePath = entry.getValue();
162             final byte[] fileBytes = contentHandler.getFileContent(filePath);
163             final byte[] fileSignatureBytes = contentHandler.getFileContent(fileSignaturePath);
164             try {
165                 if (!securityManager.verifySignedData(fileSignatureBytes, packageCert, fileBytes)) {
166                     reportError(ErrorLevel.ERROR,
167                         Messages.ARTIFACT_INVALID_SIGNATURE.formatMessage(fileSignaturePath, filePath));
168                 }
169             } catch (final SecurityManagerException e) {
170                 final String errorMessage = Messages.ARTIFACT_SIGNATURE_VALIDATION_ERROR
171                     .formatMessage(fileSignaturePath, filePath, packageCertificatePath, e.getMessage());
172                 reportError(ErrorLevel.ERROR, errorMessage);
173                 LOGGER.error(errorMessage, e);
174             }
175         });
176     }
177
178     private void verifyManifestNameAndExtension() {
179         final Map<String, String> entries = toscaMetadata.getMetaEntries();
180         final String manifestFileName = getFileName(entries.get(ETSI_ENTRY_MANIFEST.getName()));
181         final String manifestExtension = getFileExtension(entries.get(ETSI_ENTRY_MANIFEST.getName()));
182         final String mainDefinitionFileName = getFileName(entries.get(ENTRY_DEFINITIONS.getName()));
183         if (!(TOSCA_MANIFEST_FILE_EXT).equals(manifestExtension)) {
184             reportError(ErrorLevel.ERROR, Messages.MANIFEST_INVALID_EXT.getErrorMessage());
185         }
186         if (!mainDefinitionFileName.equals(manifestFileName)) {
187             reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_INVALID_NAME.getErrorMessage(),
188                 manifestFileName, mainDefinitionFileName));
189         }
190     }
191
192     private String getFileExtension(final String filePath) {
193         return FilenameUtils.getExtension(filePath);
194     }
195
196     private String getFileName(final String filePath) {
197         return FilenameUtils.getBaseName(filePath);
198     }
199
200     private boolean hasETSIMetadata() {
201         final Map<String, String> entries = toscaMetadata.getMetaEntries();
202         return hasEntry(entries, TOSCA_META_FILE_VERSION_ENTRY.getName())
203             && hasEntry(entries, CSAR_VERSION_ENTRY.getName())
204             && hasEntry(entries, CREATED_BY_ENTRY.getName());
205     }
206
207     private boolean hasEntry(final Map<String, String> entries, final String mandatoryEntry) {
208         if (!entries.containsKey(mandatoryEntry)) {
209             reportError(ErrorLevel.ERROR,
210                 String.format(Messages.METADATA_MISSING_ENTRY.getErrorMessage(), mandatoryEntry));
211             return false;
212         }
213         return true;
214     }
215
216     private void handleMetadataEntries() {
217         toscaMetadata.getMetaEntries().entrySet().parallelStream().forEach(this::handleEntry);
218     }
219
220     private void handleEntry(final Map.Entry<String, String> entry) {
221         final String key = entry.getKey();
222         final ToscaMetaEntry toscaMetaEntry = ToscaMetaEntry.parse(entry.getKey()).orElse(null);
223         // allows any other unknown entry
224         if (toscaMetaEntry == null) {
225             return;
226         }
227         final String value = entry.getValue();
228
229         switch (toscaMetaEntry) {
230             case TOSCA_META_FILE_VERSION_ENTRY:
231             case CSAR_VERSION_ENTRY:
232             case CREATED_BY_ENTRY:
233                 verifyMetadataEntryVersions(key, value);
234                 break;
235             case ENTRY_DEFINITIONS:
236                 validateDefinitionFile(value);
237                 break;
238             case ETSI_ENTRY_MANIFEST:
239                 validateManifestFile(value);
240                 break;
241             case ETSI_ENTRY_CHANGE_LOG:
242                 validateChangeLog(value);
243                 break;
244             case ETSI_ENTRY_TESTS:
245             case ETSI_ENTRY_LICENSES:
246                 validateOtherEntries(entry);
247                 break;
248             case ETSI_ENTRY_CERTIFICATE:
249                 validateCertificate(value);
250                 break;
251             default:
252                 reportError(ErrorLevel.ERROR, Messages.METADATA_UNSUPPORTED_ENTRY.formatMessage(key));
253                 LOGGER.warn(Messages.METADATA_UNSUPPORTED_ENTRY.getErrorMessage(), key);
254                 break;
255         }
256     }
257
258     private void validateOtherEntries(final Map.Entry entry) {
259         final String manifestFile = toscaMetadata.getMetaEntries().get(ETSI_ENTRY_MANIFEST.getName());
260         if (verifyFileExists(contentHandler.getFileList(), manifestFile)) {
261             final Manifest onboardingManifest = new SOL004ManifestOnboarding();
262             onboardingManifest.parse(contentHandler.getFileContentAsStream(manifestFile));
263             final Optional<ResourceTypeEnum> resourceType = onboardingManifest.getType();
264             if (resourceType.isPresent() && resourceType.get() == ResourceTypeEnum.VF) {
265                 final String value = (String) entry.getValue();
266                 validateOtherEntries(value);
267             } else {
268                 final String key = (String) entry.getKey();
269                 reportError(ErrorLevel.ERROR,
270                     String.format(Messages.MANIFEST_INVALID_PNF_METADATA.getErrorMessage(), key));
271             }
272
273         }
274     }
275
276     private void verifyMetadataEntryVersions(final String key, final String version) {
277         if (!(isValidTOSCAVersion(key, version) || isValidCSARVersion(key, version)
278             || CREATED_BY_ENTRY.getName().equals(key))) {
279             errorsByFile.add(new ErrorMessage(ErrorLevel.ERROR,
280                 String.format(Messages.METADATA_INVALID_VERSION.getErrorMessage(), key, version)));
281             LOGGER.error("{}: key {} - value {} ", Messages.METADATA_INVALID_VERSION.getErrorMessage(), key, version);
282         }
283     }
284
285     private boolean isValidTOSCAVersion(final String key, final String version) {
286         return TOSCA_META_FILE_VERSION_ENTRY.getName().equals(key) && TOSCA_META_FILE_VERSION_1_0.equals(version);
287     }
288
289     private boolean isValidCSARVersion(final String value, final String version) {
290         return CSAR_VERSION_ENTRY.getName().equals(value) && (CSAR_VERSION_1_1.equals(version)
291             || CSAR_VERSION_1_0.equals(version));
292     }
293
294     private void validateDefinitionFile(final String filePath) {
295         final Set<String> existingFiles = contentHandler.getFileList();
296
297         if (verifyFileExists(existingFiles, filePath)) {
298             final ToscaDefinitionImportHandler toscaDefinitionImportHandler =
299                 new ToscaDefinitionImportHandler(contentHandler.getFiles(), filePath);
300             final List<ErrorMessage> validationErrorList = toscaDefinitionImportHandler.getErrors();
301             if (CollectionUtils.isNotEmpty(validationErrorList)) {
302                 errorsByFile.addAll(validationErrorList);
303             }
304         } else {
305             reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_DEFINITION_FILE.getErrorMessage(), filePath));
306         }
307     }
308
309     private boolean verifyFileExists(final Set<String> existingFiles, final String filePath) {
310         return existingFiles.contains(filePath);
311     }
312
313     private void validateManifestFile(final String filePath) {
314         final Set<String> existingFiles = contentHandler.getFileList();
315         if (verifyFileExists(existingFiles, filePath)) {
316             final Manifest onboardingManifest = new SOL004ManifestOnboarding();
317             onboardingManifest.parse(contentHandler.getFileContentAsStream(filePath));
318             if (onboardingManifest.isValid()) {
319                 try {
320                     verifyManifestMetadata(onboardingManifest.getMetadata());
321                 } catch (final InvalidManifestMetadataException e) {
322                     reportError(ErrorLevel.ERROR, e.getMessage());
323                     LOGGER.error(e.getMessage(), e);
324                 }
325                 verifyManifestSources(onboardingManifest);
326             } else {
327                 final List<String> manifestErrors = onboardingManifest.getErrors();
328                 manifestErrors.forEach(error -> reportError(ErrorLevel.ERROR, error));
329             }
330         } else {
331             reportError(ErrorLevel.ERROR, String.format(Messages.MANIFEST_NOT_FOUND.getErrorMessage(), filePath));
332         }
333     }
334
335     private void verifyManifestMetadata(final Map<String, String> metadata) {
336         if (metadata.size() != MANIFEST_METADATA_LIMIT) {
337             reportError(ErrorLevel.ERROR,
338                 String.format(Messages.MANIFEST_METADATA_DOES_NOT_MATCH_LIMIT.getErrorMessage(),
339                     MANIFEST_METADATA_LIMIT));
340         }
341         if (isPnfMetadata(metadata)) {
342             handleMetadataEntries(metadata, MANIFEST_PNF_METADATA);
343         } else {
344             handleMetadataEntries(metadata, MANIFEST_VNF_METADATA);
345         }
346     }
347
348     private boolean isPnfMetadata(final Map<String, String> metadata) {
349         final String firstMetadataDefinition = metadata.keySet().iterator().next();
350         final String expectedMetadataType =
351             firstMetadataDefinition.contains(TOSCA_TYPE_PNF) ? TOSCA_TYPE_PNF : TOSCA_TYPE_VNF;
352         if (metadata.keySet().stream()
353             .anyMatch((final String metadataEntry) -> !metadataEntry.contains(expectedMetadataType))) {
354             throw new InvalidManifestMetadataException(Messages.MANIFEST_METADATA_INVALID_ENTRY.getErrorMessage());
355         }
356
357         return TOSCA_TYPE_PNF.equals(expectedMetadataType);
358     }
359
360     private void handleMetadataEntries(final Map<String, String> metadata, final Set<String> manifestMetadata) {
361         manifestMetadata.stream()
362             .filter(requiredEntry -> !metadata.containsKey(requiredEntry))
363             .forEach(requiredEntry ->
364                 reportError(ErrorLevel.ERROR,
365                     String.format(Messages.MANIFEST_METADATA_MISSING_ENTRY.getErrorMessage(), requiredEntry)));
366     }
367
368     /**
369      * Checks if all manifest sources exists within the package and if all package files are being referred.
370      *
371      * @param onboardingManifest The manifest
372      */
373     private void verifyManifestSources(final Manifest onboardingManifest) {
374         final Set<String> packageFiles = contentHandler.getFileList();
375         final List<String> sources = filterSources(onboardingManifest.getSources());
376         verifyFilesExist(packageFiles, sources, MANIFEST_SOURCE);
377
378         final Map<String, List<String>> nonManoArtifacts = onboardingManifest.getNonManoSources();
379
380         final List<String> nonManoValidFilePaths = new ArrayList<>();
381         nonManoArtifacts.forEach((nonManoType, files) -> {
382             final List<String> internalNonManoFileList = filterSources(files);
383             nonManoValidFilePaths.addAll(internalNonManoFileList);
384             final NonManoArtifactType nonManoArtifactType = NonManoArtifactType.parse(nonManoType).orElse(null);
385             if (nonManoArtifactType == ONAP_PM_DICTIONARY || nonManoArtifactType == ONAP_VES_EVENTS) {
386                 internalNonManoFileList.forEach(this::validateYaml);
387             } else if (nonManoArtifactType == ONAP_SW_INFORMATION) {
388                 validateSoftwareInformationNonManoArtifact(files);
389             }
390         });
391
392         verifyFilesExist(packageFiles, nonManoValidFilePaths, MANIFEST_NON_MANO_SOURCE);
393
394         final Set<String> allReferredFiles = new HashSet<>();
395         allReferredFiles.addAll(sources);
396         allReferredFiles.addAll(nonManoValidFilePaths);
397         verifyFilesBeingReferred(allReferredFiles, packageFiles);
398     }
399
400     private void validateSoftwareInformationNonManoArtifact(final List<String> files) {
401         if (CollectionUtils.isEmpty(files)) {
402             reportError(ErrorLevel.ERROR, Messages.EMPTY_SW_INFORMATION_NON_MANO_ERROR.getErrorMessage());
403             return;
404         }
405         if (files.size() != 1) {
406             final String formattedFileList = files.stream()
407                 .map(filePath -> String.format("'%s'", filePath))
408                 .collect(Collectors.joining(", "));
409             reportError(ErrorLevel.ERROR,
410                 Messages.UNIQUE_SW_INFORMATION_NON_MANO_ERROR.formatMessage(formattedFileList));
411             return;
412         }
413         final String swInformationFilePath = files.get(0);
414         final byte[] swInformationYaml = contentHandler.getFileContent(swInformationFilePath);
415         final Optional<PnfSoftwareInformation> parsedYaml = SoftwareInformationArtifactYamlParser
416             .parse(swInformationYaml);
417         if(!parsedYaml.isPresent()) {
418             reportError(ErrorLevel.ERROR,
419                 Messages.INVALID_SW_INFORMATION_NON_MANO_ERROR.formatMessage(swInformationFilePath));
420         } else {
421             final PnfSoftwareInformation pnfSoftwareInformation = parsedYaml.get();
422             if (!pnfSoftwareInformation.isValid()) {
423                 reportError(ErrorLevel.ERROR,
424                     Messages.INCORRECT_SW_INFORMATION_NON_MANO_ERROR.formatMessage(swInformationFilePath));
425             }
426         }
427     }
428
429     /**
430      * Validates if a YAML file has the correct extension, is not empty and the content is a valid YAML. Reports each
431      * error found.
432      *
433      * @param filePath the file path inside the package
434      */
435     private void validateYaml(final String filePath) {
436         if (!contentHandler.containsFile(filePath)) {
437             return;
438         }
439         final String fileExtension = getFileExtension(filePath);
440         if (!"yaml".equalsIgnoreCase(fileExtension) && !"yml".equalsIgnoreCase(fileExtension)) {
441             reportError(ErrorLevel.ERROR, Messages.INVALID_YAML_EXTENSION.formatMessage(filePath));
442             return;
443         }
444
445         try (final InputStream fileContent = contentHandler.getFileContentAsStream(filePath)) {
446             if (fileContent == null) {
447                 reportError(ErrorLevel.ERROR, Messages.EMPTY_YAML_FILE_1.formatMessage(filePath));
448                 return;
449             }
450             new Yaml().loadAll(fileContent).iterator().next();
451         } catch (final IOException e) {
452             final String errorMsg = Messages.FILE_LOAD_CONTENT_ERROR.formatMessage(filePath);
453             reportError(ErrorLevel.ERROR, errorMsg);
454             LOGGER.debug(errorMsg, e);
455         } catch (final Exception e) {
456             final String message = Messages.INVALID_YAML_FORMAT_1.formatMessage(filePath, e.getMessage());
457             LOGGER.debug(message, e);
458             reportError(ErrorLevel.ERROR, message);
459         }
460     }
461
462     /**
463      * Checks if all package files are referred in manifest. Reports missing references.
464      *
465      * @param referredFileSet the list of referred files path
466      * @param packageFileSet  the list of package file path
467      */
468     private void verifyFilesBeingReferred(final Set<String> referredFileSet, final Set<String> packageFileSet) {
469         packageFileSet.forEach(filePath -> {
470             if (!isManifestFile(filePath) && !referredFileSet.contains(filePath)) {
471                 reportError(ErrorLevel.ERROR,
472                     String.format(Messages.MISSING_MANIFEST_REFERENCE.getErrorMessage(), filePath));
473             }
474         });
475     }
476     
477     private boolean isManifestFile(final String filePath) {
478         return filePath.equals(toscaMetadata.getMetaEntries().get(ETSI_ENTRY_MANIFEST.getName()));
479     }
480
481     private List<String> filterSources(final List<String> source) {
482         return source.stream()
483             .filter(this::externalFileReferences)
484             .collect(Collectors.toList());
485     }
486
487     private boolean externalFileReferences(final String filePath) {
488         return !filePath.contains("://");
489     }
490
491     private void validateOtherEntries(final String folderPath) {
492         if (!verifyFoldersExist(folderList, folderPath)) {
493             reportError(ErrorLevel.ERROR, String.format(Messages.METADATA_MISSING_OPTIONAL_FOLDERS.getErrorMessage(),
494                 folderPath));
495         }
496     }
497
498     private void validateCertificate(final String file) {
499         final Set<String> packageFiles = contentHandler.getFileList();
500         if (!verifyFileExist(packageFiles, file)) {
501             reportError(ErrorLevel.ERROR,
502                 String.format(Messages.MISSING_METADATA_FILES.getErrorMessage(), file, file));
503         }
504     }
505
506     private boolean verifyFoldersExist(final Set<String> folderList, final String folderPath) {
507         return folderList.contains(folderPath + "/");
508     }
509
510     private void verifyFilesExist(final Set<String> existingFiles, final List<String> sources, final String type) {
511         sources.forEach(file -> {
512             if (!existingFiles.contains(file)) {
513                 reportError(ErrorLevel.ERROR,
514                     String.format(Messages.MISSING_MANIFEST_SOURCE.getErrorMessage(), type, file));
515             }
516         });
517     }
518
519     private boolean verifyFileExist(final Set<String> existingFiles, final String file) {
520         return existingFiles.contains(file);
521     }
522
523     private void validateChangeLog(final String filePath) {
524         if (!verifyFileExists(contentHandler.getFileList(), filePath)) {
525             reportError(ErrorLevel.ERROR, String.format(Messages.MISSING_METADATA_FILES.getErrorMessage(), filePath));
526         }
527     }
528
529     private void reportError(final ErrorLevel errorLevel, final String errorMessage) {
530         errorsByFile.add(new ErrorMessage(errorLevel, errorMessage));
531     }
532
533     private Map<String, List<ErrorMessage>> getAnyValidationErrors() {
534         if (errorsByFile.isEmpty()) {
535             return Collections.emptyMap();
536         }
537         final Map<String, List<ErrorMessage>> errors = new HashMap<>();
538         errors.put(SdcCommon.UPLOAD_FILE, errorsByFile);
539         return errors;
540     }
541 }