diff --git a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/Constants.java b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/Constants.java index 70d0fbc0..fdc71d68 100644 --- a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/Constants.java +++ b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/Constants.java @@ -7,4 +7,5 @@ private Constants() { public static final String PARAMETER_ELEMENT_TYPE_NAME = "parameter"; public static final String PROPERTY_ELEMENT_TYPE_NAME = "property"; + public static final String SECURE_EXTENSION_DESCRIPTOR_ID = "__mta.secure"; } diff --git a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/builders/ExtensionDescriptorChainBuilder.java b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/builders/ExtensionDescriptorChainBuilder.java index cf65634a..a75e7e24 100644 --- a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/builders/ExtensionDescriptorChainBuilder.java +++ b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/builders/ExtensionDescriptorChainBuilder.java @@ -1,21 +1,25 @@ package org.cloudfoundry.multiapps.mta.builders; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import org.apache.commons.collections4.CollectionUtils; import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.mta.Constants; import org.cloudfoundry.multiapps.mta.Messages; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; import org.cloudfoundry.multiapps.mta.model.Descriptor; import org.cloudfoundry.multiapps.mta.model.ExtensionDescriptor; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + public class ExtensionDescriptorChainBuilder { private final boolean isStrict; + private ExtensionDescriptor secureExtensionDescriptor; + public ExtensionDescriptorChainBuilder() { this(true); } @@ -24,8 +28,14 @@ public ExtensionDescriptorChainBuilder(boolean isStrict) { this.isStrict = isStrict; } + private boolean isSecureDescriptor(ExtensionDescriptor extensionDescriptor) { + return extensionDescriptor.getId() + .equals(Constants.SECURE_EXTENSION_DESCRIPTOR_ID); + } + public List build(DeploymentDescriptor deploymentDescriptor, List extensionDescriptors) throws ContentException { + saveSecureExtensionDescriptor(extensionDescriptors); Map extensionDescriptorsPerParent = getExtensionDescriptorsPerParent(extensionDescriptors); return build(deploymentDescriptor, extensionDescriptorsPerParent); } @@ -34,40 +44,70 @@ private List build(DeploymentDescriptor deploymentDescripto Map extensionDescriptorsPerParent) { List chain = new ArrayList<>(); Descriptor currentDescriptor = deploymentDescriptor; + while (currentDescriptor != null) { ExtensionDescriptor nextDescriptor = extensionDescriptorsPerParent.remove(currentDescriptor.getId()); CollectionUtils.addIgnoreNull(chain, nextDescriptor); currentDescriptor = nextDescriptor; } + + CollectionUtils.addIgnoreNull(chain, this.secureExtensionDescriptor); + if (!extensionDescriptorsPerParent.isEmpty() && isStrict) { throw new ContentException(Messages.CANNOT_BUILD_EXTENSION_DESCRIPTOR_CHAIN_BECAUSE_DESCRIPTORS_0_HAVE_AN_UNKNOWN_PARENT, String.join(",", Descriptor.getIds(extensionDescriptorsPerParent.values()))); } + return chain; } private Map getExtensionDescriptorsPerParent(List extensionDescriptors) { Map> extensionDescriptorsPerParent = extensionDescriptors.stream() - .collect(Collectors.groupingBy(ExtensionDescriptor::getParentId)); + .collect(Collectors.groupingBy( + ExtensionDescriptor::getParentId)); + validateSingleExtensionDescriptorPerParent(extensionDescriptorsPerParent); return prune(extensionDescriptorsPerParent); } private Map prune(Map> extensionDescriptorsPerParent) { - validateSingleExtensionDescriptorPerParent(extensionDescriptorsPerParent); - return extensionDescriptorsPerParent.entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue() - .get(0))); + Map resultMap = new LinkedHashMap<>(); + + for (Map.Entry> entry : extensionDescriptorsPerParent.entrySet()) { + for (ExtensionDescriptor currentExtensionDescriptorForParent : entry.getValue()) { + if (!isSecureDescriptor(currentExtensionDescriptorForParent)) { + resultMap.put(entry.getKey(), currentExtensionDescriptorForParent); + break; + } + } + } + return resultMap; } private void validateSingleExtensionDescriptorPerParent(Map> extensionDescriptorsPerParent) { for (Map.Entry> extensionDescriptorsForParent : extensionDescriptorsPerParent.entrySet()) { String parent = extensionDescriptorsForParent.getKey(); List extensionDescriptors = extensionDescriptorsForParent.getValue(); - if (extensionDescriptors.size() > 1 && isStrict) { + long nonSecureCountOfExtensionDescriptors = extensionDescriptors.stream() + .filter(descriptor -> !descriptor.getId() + .equals( + Constants.SECURE_EXTENSION_DESCRIPTOR_ID)) + .count(); + if (nonSecureCountOfExtensionDescriptors > 1 && isStrict) { throw new ContentException(Messages.MULTIPLE_EXTENSION_DESCRIPTORS_EXTEND_THE_PARENT_0, parent); } } } -} + private void saveSecureExtensionDescriptor(List extensionDescriptors) { + ExtensionDescriptor lastSecureExtensionDescriptor = null; + + for (ExtensionDescriptor extensionDescriptor : extensionDescriptors) { + if (isSecureDescriptor(extensionDescriptor)) { + lastSecureExtensionDescriptor = extensionDescriptor; + } + } + + this.secureExtensionDescriptor = lastSecureExtensionDescriptor; + } + +} \ No newline at end of file diff --git a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/ExtensionHook.java b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/ExtensionHook.java index eba87204..21a97e75 100644 --- a/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/ExtensionHook.java +++ b/multiapps-mta/src/main/java/org/cloudfoundry/multiapps/mta/model/ExtensionHook.java @@ -1,14 +1,14 @@ package org.cloudfoundry.multiapps.mta.model; -import java.util.Collections; -import java.util.List; -import java.util.Map; - import org.apache.commons.lang3.ObjectUtils; import org.cloudfoundry.multiapps.common.util.yaml.YamlElement; import org.cloudfoundry.multiapps.mta.parsers.v3.ExtensionHookParser; -public class ExtensionHook extends VersionedEntity implements VisitableElement, NamedElement { +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class ExtensionHook extends VersionedEntity implements VisitableElement, NamedElement, ParametersContainer { // Required by Jackson. protected ExtensionHook() { diff --git a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/builders/ExtensionDescriptorChainBuilderTest.java b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/builders/ExtensionDescriptorChainBuilderTest.java index 1911e7e1..e238e7cb 100644 --- a/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/builders/ExtensionDescriptorChainBuilderTest.java +++ b/multiapps-mta/src/test/java/org/cloudfoundry/multiapps/mta/builders/ExtensionDescriptorChainBuilderTest.java @@ -1,18 +1,20 @@ package org.cloudfoundry.multiapps.mta.builders; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.mta.Constants; +import org.cloudfoundry.multiapps.mta.Messages; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.cloudfoundry.multiapps.mta.model.ExtensionDescriptor; +import org.junit.jupiter.api.Test; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.cloudfoundry.multiapps.common.ContentException; -import org.cloudfoundry.multiapps.mta.Messages; -import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; -import org.cloudfoundry.multiapps.mta.model.ExtensionDescriptor; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; class ExtensionDescriptorChainBuilderTest { @@ -50,8 +52,9 @@ void testBuildWithDescriptorWithUnknownParent() { ContentException contentException = assertThrows(ContentException.class, () -> extensionDescriptorChainBuilder.build(deploymentDescriptor, extensionDescriptors)); - String expectedMessage = MessageFormat.format(Messages.CANNOT_BUILD_EXTENSION_DESCRIPTOR_CHAIN_BECAUSE_DESCRIPTORS_0_HAVE_AN_UNKNOWN_PARENT, - extensionDescriptor2.getId()); + String expectedMessage = MessageFormat.format( + Messages.CANNOT_BUILD_EXTENSION_DESCRIPTOR_CHAIN_BECAUSE_DESCRIPTORS_0_HAVE_AN_UNKNOWN_PARENT, + extensionDescriptor2.getId()); assertEquals(expectedMessage, contentException.getMessage()); } @@ -102,6 +105,96 @@ void testLaxBuildWithMultipleDescriptorsExtendingTheSameParent() { assertEquals(Collections.emptyList(), extensionDescriptorChain); } + @Test + void testBuildWithNormalAndSecureExtensionDescriptorsWhereSecureIsLastInTheChain() { + DeploymentDescriptor deploymentDescriptor = buildDeploymentDescriptor(FOO_ID); + ExtensionDescriptor normalExtensionDescriptor = buildExtensionDescriptor(BAR_ID, FOO_ID); + ExtensionDescriptor secureExtensionDescriptor = buildSecureExtensionDescriptor(FOO_ID); + + List extensionDescriptors = List.of(secureExtensionDescriptor, normalExtensionDescriptor); + + ExtensionDescriptorChainBuilder extensionDescriptorChainBuilder = new ExtensionDescriptorChainBuilder(); + List extensionDescriptorChain = extensionDescriptorChainBuilder.build(deploymentDescriptor, + extensionDescriptors); + + assertEquals(List.of(normalExtensionDescriptor, secureExtensionDescriptor), extensionDescriptorChain); + } + + @Test + void testBuildWithOnlySecureExtensionDescriptor() { + DeploymentDescriptor deploymentDescriptor = buildDeploymentDescriptor(FOO_ID); + ExtensionDescriptor secureExtensionDescriptor = buildSecureExtensionDescriptor(FOO_ID); + + List extensionDescriptors = Collections.singletonList(secureExtensionDescriptor); + + ExtensionDescriptorChainBuilder extensionDescriptorChainBuilder = new ExtensionDescriptorChainBuilder(); + List extensionDescriptorChain = extensionDescriptorChainBuilder.build(deploymentDescriptor, + extensionDescriptors); + + assertEquals(Collections.singletonList(secureExtensionDescriptor), extensionDescriptorChain); + } + + @Test + void testBuildWhenSecureExtensionDescriptorIsExtendedStrictMode() { + DeploymentDescriptor deploymentDescriptor = buildDeploymentDescriptor(FOO_ID); + ExtensionDescriptor firstExtensionDescriptor = buildExtensionDescriptor(BAR_ID, FOO_ID); + ExtensionDescriptor secureExtensionDescriptor = buildSecureExtensionDescriptor(BAR_ID); + ExtensionDescriptor extensionDescriptorAfterSecureOne = buildExtensionDescriptor(QUX_ID, Constants.SECURE_EXTENSION_DESCRIPTOR_ID); + + List extensionDescriptors = List.of(extensionDescriptorAfterSecureOne, firstExtensionDescriptor, + secureExtensionDescriptor); + + ExtensionDescriptorChainBuilder extensionDescriptorChainBuilder = new ExtensionDescriptorChainBuilder(true); + + ContentException contentException = assertThrows(ContentException.class, + () -> extensionDescriptorChainBuilder.build(deploymentDescriptor, + extensionDescriptors)); + + String expectedMessage = MessageFormat.format( + Messages.CANNOT_BUILD_EXTENSION_DESCRIPTOR_CHAIN_BECAUSE_DESCRIPTORS_0_HAVE_AN_UNKNOWN_PARENT, + extensionDescriptorAfterSecureOne.getId() + ); + assertEquals(expectedMessage, contentException.getMessage()); + } + + @Test + void testBuildWhenSecureExtensionDescriptorIsExtendedNonStrictMode() { + DeploymentDescriptor deploymentDescriptor = buildDeploymentDescriptor(FOO_ID); + ExtensionDescriptor firstExtensionDescriptor = buildExtensionDescriptor(BAR_ID, FOO_ID); + ExtensionDescriptor secureExtensionDescriptor = buildSecureExtensionDescriptor(BAR_ID); + ExtensionDescriptor extensionDescriptorAfterSecureOne = buildExtensionDescriptor(QUX_ID, Constants.SECURE_EXTENSION_DESCRIPTOR_ID); + + List extensionDescriptors = List.of(extensionDescriptorAfterSecureOne, secureExtensionDescriptor, + firstExtensionDescriptor); + + ExtensionDescriptorChainBuilder extensionDescriptorChainBuilder = new ExtensionDescriptorChainBuilder(false); + List extensionDescriptorChain = extensionDescriptorChainBuilder.build(deploymentDescriptor, + extensionDescriptors); + + assertEquals(List.of(firstExtensionDescriptor, secureExtensionDescriptor), extensionDescriptorChain); + } + + @Test + void testBuildWhenTwoSecureExtensionDescriptorsWhereOnlyTheLastOneGetsAppended() { + DeploymentDescriptor deploymentDescriptor = buildDeploymentDescriptor(FOO_ID); + ExtensionDescriptor firstExtensionDescriptor = buildExtensionDescriptor(BAR_ID, FOO_ID); + ExtensionDescriptor firstSecureExtensionDescriptor = buildSecureExtensionDescriptor(FOO_ID); + ExtensionDescriptor secondSecureExtensionDescriptor = buildSecureExtensionDescriptor(FOO_ID); + + List extensionDescriptors = List.of(firstSecureExtensionDescriptor, firstExtensionDescriptor, + secondSecureExtensionDescriptor); + + ExtensionDescriptorChainBuilder extensionDescriptorChainBuilder = new ExtensionDescriptorChainBuilder(); + List extensionDescriptorChain = extensionDescriptorChainBuilder.build(deploymentDescriptor, + extensionDescriptors); + + assertEquals(2, extensionDescriptorChain.size()); + assertEquals(firstExtensionDescriptor, extensionDescriptorChain.get(0)); + assertEquals(Constants.SECURE_EXTENSION_DESCRIPTOR_ID, extensionDescriptorChain.get(1) + .getId()); + assertSame(secondSecureExtensionDescriptor, extensionDescriptorChain.get(1)); + } + private DeploymentDescriptor buildDeploymentDescriptor(String id) { return DeploymentDescriptor.createV2() .setId(id); @@ -113,4 +206,10 @@ private ExtensionDescriptor buildExtensionDescriptor(String id, String parentId) .setId(id); } + private ExtensionDescriptor buildSecureExtensionDescriptor(String parentId) { + return ExtensionDescriptor.createV2() + .setParentId(parentId) + .setId(Constants.SECURE_EXTENSION_DESCRIPTOR_ID); + } + }