From 22360c78d550a25b9bdaea12cdb208371b69a488 Mon Sep 17 00:00:00 2001 From: "andre.schmid" Date: Fri, 12 Jul 2019 12:33:10 +0000 Subject: Allow relative path for SOL004 descriptors import Allow the use of relative path on SOL004 descriptors imports. Resolves imports with "/", "../" or "./" entries during validation and package processing. Validate if the reference is inside the package. Fix problem where imported descriptor files, described as a non string scalar yaml entry, were not being checked by the validator. Change-Id: Ie5a32736b6090b4adf178e8714f7460bcd068def Issue-ID: SDC-2422 Signed-off-by: andre.schmid --- .../core/impl/AbstractToscaSolConverter.java | 48 +---- .../InvalidToscaDefinitionImportException.java | 55 +++++ .../core/impl/ToscaDefinitionImportHandler.java | 231 +++++++++++++++++++++ 3 files changed, 296 insertions(+), 38 deletions(-) create mode 100644 openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/InvalidToscaDefinitionImportException.java create mode 100644 openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/ToscaDefinitionImportHandler.java (limited to 'openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java') diff --git a/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/AbstractToscaSolConverter.java b/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/AbstractToscaSolConverter.java index 6371ba67d9..f0d8bb2b3c 100644 --- a/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/AbstractToscaSolConverter.java +++ b/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/AbstractToscaSolConverter.java @@ -23,8 +23,6 @@ package org.openecomp.core.impl; import org.onap.sdc.tosca.datatypes.model.ServiceTemplate; -import org.openecomp.core.converter.ServiceTemplateReaderService; -import org.openecomp.core.impl.services.ServiceTemplateReaderServiceImpl; import org.openecomp.core.utilities.file.FileContentHandler; import org.openecomp.sdc.logging.api.Logger; import org.openecomp.sdc.logging.api.LoggerFactory; @@ -35,12 +33,10 @@ import org.openecomp.sdc.tosca.datatypes.ToscaServiceModel; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import static org.openecomp.core.converter.datatypes.Constants.globalStName; -import static org.openecomp.sdc.tosca.csar.CSARConstants.NON_FILE_IMPORT_ATTRIBUTES; import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_ENTRY_DEFINITIONS; import static org.openecomp.sdc.tosca.csar.CSARConstants.TOSCA_META_PATH_FILE_NAME; @@ -68,8 +64,7 @@ public abstract class AbstractToscaSolConverter extends AbstractToscaConverter { GlobalSubstitutionServiceTemplate gsst, String mServiceDefinitionFileName) { if (mServiceDefinitionFileName != null) { handleServiceTemplate(getSimpleName(mServiceDefinitionFileName), mServiceDefinitionFileName, csarFiles, serviceTemplates); - String parentDir = mServiceDefinitionFileName.substring(0, mServiceDefinitionFileName.lastIndexOf("/")); - handleImportDefinitions(mServiceDefinitionFileName, csarFiles, parentDir, gsst); + handleImportDefinitions(mServiceDefinitionFileName, csarFiles, gsst); } } @@ -86,39 +81,16 @@ public abstract class AbstractToscaSolConverter extends AbstractToscaConverter { } } - private void handleImportDefinitions(String fileName, Map csarFiles, String parentDir, GlobalSubstitutionServiceTemplate gsst) { - handledDefinitionFilesList.add(fileName); - ServiceTemplateReaderService readerService = new ServiceTemplateReaderServiceImpl(csarFiles.get(fileName)); - List imports = (readerService).getImports(); - for (Object o : imports) { - String importPath = getImportedFilePath(o, parentDir); - if (importPath != null && !handledDefinitionFilesList.contains(importPath)) { - handleDefintionTemplate(importPath, csarFiles, gsst); - if (importPath.contains("/")) { - parentDir = importPath.substring(0, importPath.lastIndexOf("/")); - } - handleImportDefinitions(importPath, csarFiles, parentDir, gsst); - } + private void handleImportDefinitions(final String fileName, final Map csarFiles + , final GlobalSubstitutionServiceTemplate gsst) { + final ToscaDefinitionImportHandler toscaDefinitionImportHandler = new ToscaDefinitionImportHandler(csarFiles, fileName); + if (toscaDefinitionImportHandler.hasError()) { + throw new InvalidToscaDefinitionImportException(toscaDefinitionImportHandler.getErrors()); } - return; - } - - private String getImportedFilePath(Object o, String parentDir) { - if (o instanceof String) { - String fileName = (String) o; - if (!fileName.contains("/")) { - fileName = parentDir + "/" + fileName; - } - return fileName; - } else if (o instanceof Map) { - Map o1 = (Map) o; - for (Map.Entry entry : o1.entrySet()) { - if (NON_FILE_IMPORT_ATTRIBUTES.stream().noneMatch(attr -> entry.getKey().equals(attr))) { - getImportedFilePath(entry.getValue(), parentDir); - } - } + handledDefinitionFilesList.addAll(toscaDefinitionImportHandler.getHandledDefinitionFilesList()); + for (final String file : handledDefinitionFilesList) { + handleDefintionTemplate(file, csarFiles, gsst); } - return null; } private String getMainServiceDefinitionFileName(FileContentHandler contentHandler) throws IOException { @@ -134,7 +106,7 @@ public abstract class AbstractToscaSolConverter extends AbstractToscaConverter { private String getSimpleName(String path) { if (path != null && path.contains("/")) { - path = path.substring(path.lastIndexOf("/") + 1); + path = path.substring(path.lastIndexOf('/') + 1); } return path; } diff --git a/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/InvalidToscaDefinitionImportException.java b/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/InvalidToscaDefinitionImportException.java new file mode 100644 index 0000000000..ac2e5eccc2 --- /dev/null +++ b/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/InvalidToscaDefinitionImportException.java @@ -0,0 +1,55 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2019 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.core.impl; + +import java.util.List; +import java.util.StringJoiner; +import org.apache.commons.collections.CollectionUtils; +import org.openecomp.sdc.datatypes.error.ErrorMessage; + +/** + * Runtime exception for errors in import statements inside a TOSCA definition yaml file. + */ +public class InvalidToscaDefinitionImportException extends RuntimeException { + + private final String message; + + /** + * Builds the exception message based on the provided validation error list. + * @param validationErrorList The error list + */ + public InvalidToscaDefinitionImportException(final List validationErrorList) { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("The provided package is invalid as it contains descriptors import errors:\n"); + if (CollectionUtils.isNotEmpty(validationErrorList)) { + final StringJoiner joiner = new StringJoiner(";\n"); + validationErrorList.forEach( + errorMessage -> joiner.add(String.format("%s: %s", errorMessage.getLevel(), errorMessage.getMessage()))); + message = stringBuilder.append(joiner.toString()).toString(); + } else { + message = stringBuilder.toString(); + } + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/ToscaDefinitionImportHandler.java b/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/ToscaDefinitionImportHandler.java new file mode 100644 index 0000000000..8422c89f2e --- /dev/null +++ b/openecomp-be/lib/openecomp-tosca-converter-lib/openecomp-tosca-converter-core/src/main/java/org/openecomp/core/impl/ToscaDefinitionImportHandler.java @@ -0,0 +1,231 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2019 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.core.impl; + +import static org.openecomp.sdc.tosca.csar.CSARConstants.NON_FILE_IMPORT_ATTRIBUTES; + +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.openecomp.core.converter.ServiceTemplateReaderService; +import org.openecomp.core.impl.services.ServiceTemplateReaderServiceImpl; +import org.openecomp.sdc.common.errors.Messages; +import org.openecomp.sdc.datatypes.error.ErrorLevel; +import org.openecomp.sdc.datatypes.error.ErrorMessage; + +/** + * Handles TOSCA definition imports, checking for import definition errors. + */ +public class ToscaDefinitionImportHandler { + + private final Map fileMap; + private final Set handledDefinitionFilesList = new LinkedHashSet<>(); + private final List validationErrorList = new ArrayList<>(); + private String currentFile; + + /** + * Reads the provided package structure starting from a main definition yaml file. + * @param fileStructureMap The package structure with file path and respective file byte + * @param mainDefinitionFilePath The main descriptor yaml file to start the reading + */ + public ToscaDefinitionImportHandler(final Map fileStructureMap, final String mainDefinitionFilePath) { + this.fileMap = fileStructureMap; + handleImports(mainDefinitionFilePath); + } + + /** + * Reads and validates the descriptor imports recursively. + * Starts from the provided descriptor and goes until the end of the import tree. + * Processes each file just once. + * + * @param fileName the descriptor file path + */ + private void handleImports(final String fileName) { + currentFile = fileName; + if (!checkImportExists(fileName)) { + return; + } + final ServiceTemplateReaderService readerService; + try { + readerService = new ServiceTemplateReaderServiceImpl(fileMap.get(fileName)); + } catch (final Exception ex) { + reportError(ErrorLevel.ERROR, + String.format(Messages.INVALID_YAML_FORMAT.getErrorMessage(), ex.getMessage())); + return; + } + handledDefinitionFilesList.add(fileName); + final List imports = readerService.getImports(); + final List extractImportFiles = extractFileImports(imports); + for (final String importedFile : extractImportFiles) { + final String resolvedPath = resolveImportPath(FilenameUtils.getPath(fileName), importedFile); + if (!handledDefinitionFilesList.contains(resolvedPath)) { + handleImports(resolvedPath); + } + } + } + + /** + * Iterates reads each import statement in the given list. + *
+     * example of a descriptor.yaml import statement
+     * imports:
+     * - /Artifacts/anImportedDescriptor.yaml
+     * - anotherDescriptor: anotherImportedDescriptor.yaml
+     * - yetAnotherDescriptor:
+     *     yetAnotherDescriptor: ../Definitions/yetAnotherDescriptor.yaml
+     * 
+ * @param imports the import statements + * @return + * The list of import file paths found + */ + private List extractFileImports(final List imports) { + final List importedFileList = new ArrayList<>(); + imports.forEach(importObject -> importedFileList.addAll(readImportStatement(importObject))); + + return importedFileList; + } + + /** + * Reads an import statement which can be a value, a [key:value] or a [key:[key:value]]. + * Ignores entries which contains the same keys as + * {@link org.openecomp.sdc.tosca.csar.CSARConstants#NON_FILE_IMPORT_ATTRIBUTES}. + * Reports invalid import statements. + *
+     * example of yaml imports statements:
+     * - /Artifacts/anImportedDescriptor.yaml
+     * - anotherDescriptor: anotherImportedDescriptor.yaml
+     * - yetAnotherDescriptor:
+     *     yetAnotherDescriptor: ../Definitions/yetAnotherDescriptor.yaml
+     * 
+ * @param importObject the object representing the yaml import statement + * @return + * The list of import file paths found + */ + private List readImportStatement(final Object importObject) { + final List importedFileList = new ArrayList<>(); + if (importObject instanceof String) { + importedFileList.add((String) importObject); + } else if (importObject instanceof Map) { + final Map importObjectMap = (Map) importObject; + for (final Map.Entry entry : importObjectMap.entrySet()) { + if (NON_FILE_IMPORT_ATTRIBUTES.stream().noneMatch(attr -> entry.getKey().equals(attr))) { + importedFileList.addAll(readImportStatement(entry.getValue())); + } + } + } else { + reportError(ErrorLevel.ERROR, + String.format(Messages.INVALID_IMPORT_STATEMENT.getErrorMessage(), currentFile, importObject)); + } + + return importedFileList; + } + + /** + * Given a directory path, resolves the import path. + * @param directoryPath A directory path to resolve the import path + * @param importPath An import statement path + * @return + * The resolved path of the import, using as base the directory path + */ + private String resolveImportPath(final String directoryPath, final String importPath) { + final String fixedParentDir; + if (StringUtils.isEmpty(directoryPath)) { + fixedParentDir = "/"; + } else { + fixedParentDir = String.format("%s%s%s", + directoryPath.startsWith("/") ? "" : "/" + , directoryPath + , directoryPath.endsWith("/") ? "" : "/"); + } + + final URI parentDirUri = URI.create(fixedParentDir); + + String resolvedImportPath = parentDirUri.resolve(importPath).toString(); + if (resolvedImportPath.contains("../")) { + reportError(ErrorLevel.ERROR, + Messages.INVALID_IMPORT_STATEMENT.formatMessage(currentFile, importPath)); + return null; + } + if (resolvedImportPath.startsWith("/")) { + resolvedImportPath = resolvedImportPath.substring(1); + } + + return resolvedImportPath; + } + + /** + * Checks if the given file path exists inside the file structure. + * Reports an error if the file was not found. + * + * @param filePath file path to check inside the file structure + * @return + * {@code true} if the file exists, {@code false} otherwise + */ + private boolean checkImportExists(final String filePath) { + if (!fileMap.keySet().contains(filePath)) { + reportError(ErrorLevel.ERROR, Messages.MISSING_IMPORT_FILE.formatMessage(filePath)); + return false; + } + + return true; + } + + /** + * Gets all processed files during the import handling. + * @return + * A list containing the processed files paths + */ + public Set getHandledDefinitionFilesList() { + return handledDefinitionFilesList; + } + + /** + * Adds an error to the validation error list. + * + * @param errorLevel the error level + * @param errorMessage the error message + */ + private void reportError(final ErrorLevel errorLevel, final String errorMessage) { + validationErrorList.add(new ErrorMessage(errorLevel, errorMessage)); + } + + /** + * Gets the list of errors. + * @return + * The import validation errors detected + */ + public List getErrors() { + return validationErrorList; + } + + /** + * Checks if the handler detected a import error. + * @return + * {@code true} if the handler detected any error, {@code false} otherwise. + */ + public boolean hasError() { + return !validationErrorList.isEmpty(); + } +} -- cgit 1.2.3-korg