Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,11 @@ protected ModelBuildingResult build(ModelBuildingRequest request, Collection<Str
Collection<String> parentIds = new LinkedHashSet<>();
List<ModelData> lineage = new ArrayList<>();

// Models built to resolve a dependency (a dependency POM, one of its parents, or an
// Models resolved for dependency, parent or BOM POMs evaluate only platform-derived
// activation (JDK, OS, activeByDefault).
boolean externalModel = isExternalModelBuildingRequest(request);

for (ModelData currentData = resultData; currentData != null; ) {
lineage.add(currentData);

Expand All @@ -307,8 +312,10 @@ protected ModelBuildingResult build(ModelBuildingRequest request, Collection<Str
List<Profile> interpolatedProfiles = getInterpolatedProfiles(rawModel, profileActivationContext, problems);
tmpModel.setProfiles(interpolatedProfiles);

List<Profile> activePomProfiles =
profileSelector.getActiveProfiles(tmpModel.getProfiles(), profileActivationContext, problems);
List<Profile> activePomProfiles = profileSelector.getActiveProfiles(
externalModel ? withoutFileAndPropertyActivation(interpolatedProfiles) : tmpModel.getProfiles(),
profileActivationContext,
problems);

List<Profile> rawProfiles = new ArrayList<>();
for (Profile activePomProfile : activePomProfiles) {
Expand All @@ -318,7 +325,11 @@ protected ModelBuildingResult build(ModelBuildingRequest request, Collection<Str

// profile injection
for (Profile activeProfile : activePomProfiles) {
profileInjector.injectProfile(tmpModel, activeProfile, request, problems);
profileInjector.injectProfile(
tmpModel,
externalModel ? withoutRepositories(activeProfile) : activeProfile,
request,
problems);
}

if (currentData == resultData) {
Expand Down Expand Up @@ -492,6 +503,45 @@ void performFor(String value, String locationKey, Consumer<String> mutator) {
return interpolatedActivations;
}

/**
* Determines whether the given request builds a model to resolve a dependency, i.e. a POM
* read from a remote repository (a dependency POM, one of its parents, or an imported BOM)
* rather than a POM belonging to the project being built. Such requests use
* {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL}, see for instance
* {@code DefaultArtifactDescriptorReader#loadPom}; a project build uses
* {@link ModelBuildingRequest#VALIDATION_LEVEL_MAVEN_2_0} or higher.
*/
private static boolean isExternalModelBuildingRequest(ModelBuildingRequest request) {
return request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0;
}

/**
* Returns the profiles from the given list whose activation does not depend on a file or a
* property. Profiles activated by JDK version, operating system, or marked
* {@code activeByDefault} are unaffected, since those conditions are a function of the build
* platform rather than of the model content.
*/
private static List<Profile> withoutFileAndPropertyActivation(List<Profile> profiles) {
List<Profile> eligible = new ArrayList<>(profiles.size());
for (Profile profile : profiles) {
Activation activation = profile.getActivation();
if (activation == null || (activation.getFile() == null && activation.getProperty() == null)) {
eligible.add(profile);
}
}
return eligible;
}

/**
* Returns a copy of the given profile with its repositories and plugin repositories cleared.
*/
private static Profile withoutRepositories(Profile profile) {
Profile stripped = profile.clone();
stripped.setRepositories(Collections.emptyList());
stripped.setPluginRepositories(Collections.emptyList());
return stripped;
}

@Override
public ModelBuildingResult build(ModelBuildingRequest request, ModelBuildingResult result)
throws ModelBuildingException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@
* @author jdcasey Created on Feb 3, 2005
*/
public abstract class AbstractStringBasedModelInterpolator implements ModelInterpolator {

/**
* User property for opting back into the previous behavior of interpolating
* repository-resolved models (built at {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL})
* against the full set of session properties (system, environment and CLI).
* When set to {@code "false"} (default), such models are interpolated only against
* their own {@code <properties>}, preventing property leaking from the requesting
* build into transitive POMs. When set to {@code "true"}, full interpolation is
* applied as in previous Maven versions.
* <p>
* In Maven 4.x this constant is promoted to
* {@code org.apache.maven.api.Constants.MAVEN_MODEL_DEPENDENCY_INTERPOLATION_FULL}
* with {@code @Config} so it appears in the auto-generated configuration documentation.
*/
public static final String FULL_EXTERNAL_INTERPOLATION_PROPERTY = "maven.model.dependencyInterpolation.full";

private static final List<String> PROJECT_PREFIXES = Arrays.asList("pom.", "project.");

private static final Collection<String> TRANSLATED_PATH_EXPRESSIONS;
Expand Down Expand Up @@ -180,21 +196,34 @@ public Object getValue(String expression) {

valueSources.add(modelValueSource1);

valueSources.add(new MapBasedValueSource(config.getUserProperties()));
// Models built at VALIDATION_LEVEL_MINIMAL are the models Maven builds while resolving
// dependency, parent and BOM-import POMs from a repository, not the operator's own
// project. Such models interpolate only against their own properties and a small set
// of environment-independent expressions; everything else in the user/system property
// space stays uninterpolated. Operator project builds use a higher validation level and
// keep the full set of value sources, unchanged from previous behavior.
boolean restricted = restrictExternalModelInterpolation(config);

ValueSource userPropertiesValueSource = new MapBasedValueSource(config.getUserProperties());
valueSources.add(restricted ? restrictToSafeExpressions(userPropertiesValueSource) : userPropertiesValueSource);

// Overwrite existing values in model properties. Otherwise it's not possible
// to define them via command line e.g.: mvn -Drevision=6.5.7 ...
versionProcessor.overwriteModelProperties(modelProperties, config);
valueSources.add(new MapBasedValueSource(modelProperties));

valueSources.add(new MapBasedValueSource(config.getSystemProperties()));

valueSources.add(new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
return config.getSystemProperties().getProperty("env." + expression);
}
});
ValueSource systemPropertiesValueSource = new MapBasedValueSource(config.getSystemProperties());
valueSources.add(
restricted ? restrictToSafeExpressions(systemPropertiesValueSource) : systemPropertiesValueSource);

if (!restricted) {
valueSources.add(new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
return config.getSystemProperties().getProperty("env." + expression);
}
});
}

valueSources.add(modelValueSource2);

Expand All @@ -204,6 +233,38 @@ public Object getValue(String expression) {
return valueSources;
}

private static boolean restrictExternalModelInterpolation(ModelBuildingRequest config) {
return config.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0
&& !Boolean.parseBoolean(config.getSystemProperties().getProperty(FULL_EXTERNAL_INTERPOLATION_PROPERTY))
&& !Boolean.parseBoolean(config.getUserProperties().getProperty(FULL_EXTERNAL_INTERPOLATION_PROPERTY));
}

private static ValueSource restrictToSafeExpressions(ValueSource source) {
return new AbstractValueSource(false) {
@Override
public Object getValue(String expression) {
return isSafeExternalExpression(expression) ? source.getValue(expression) : null;
}
};
}

/**
* Expressions that models built at {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL}
* may still resolve from the session properties: JVM- and Maven-defined properties, plus
* the CI-friendly version properties (MNG-5895). All other expressions are left literal.
*/
private static boolean isSafeExternalExpression(String expression) {
return expression.startsWith("java.")
|| expression.startsWith("os.")
|| expression.startsWith("maven.")
|| "file.separator".equals(expression)
|| "path.separator".equals(expression)
|| "line.separator".equals(expression)
|| "revision".equals(expression)
|| "changelist".equals(expression)
|| "sha1".equals(expression);
}

protected List<? extends InterpolationPostProcessor> createPostProcessors(
final Model model, final File projectDir, final ModelBuildingRequest config) {
List<InterpolationPostProcessor> processors = new ArrayList<>(2);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.maven.model.building;

import java.util.Properties;

import org.apache.maven.model.Model;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Models built at {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL} come from POMs resolved
* from a repository during dependency resolution (a dependency POM, one of its parents, or an
* imported BOM), see for instance {@code DefaultArtifactDescriptorReader#loadPom}. Their file and
* property activators are not evaluated, and their profiles contribute no repositories. A project
* build, at {@link ModelBuildingRequest#VALIDATION_LEVEL_STRICT}, still evaluates every activator.
* Platform-derived activation (JDK version, operating system, activeByDefault) is unaffected at
* either level.
*/
class ResolvedDependencyProfileActivationTest {

private static final String POM = "<project>\n"
+ " <modelVersion>4.0.0</modelVersion>\n"
+ " <groupId>thegroup</groupId>\n"
+ " <artifactId>withprofiles</artifactId>\n"
+ " <version>1</version>\n"
+ " <packaging>pom</packaging>\n"
+ " <profiles>\n"
+ " <profile>\n"
+ " <id>file-condition</id>\n"
+ " <activation>\n"
+ " <file>\n"
+ " <exists>${some.dir}</exists>\n"
+ " </file>\n"
+ " </activation>\n"
+ " <properties>\n"
+ " <profile.file>activated</profile.file>\n"
+ " </properties>\n"
+ " </profile>\n"
+ " <profile>\n"
+ " <id>property-condition</id>\n"
+ " <activation>\n"
+ " <property>\n"
+ " <name>some.gating.property</name>\n"
+ " </property>\n"
+ " </activation>\n"
+ " <properties>\n"
+ " <profile.property>activated</profile.property>\n"
+ " </properties>\n"
+ " </profile>\n"
+ " <profile>\n"
+ " <id>jdk-condition</id>\n"
+ " <activation>\n"
+ " <jdk>[1,)</jdk>\n"
+ " </activation>\n"
+ " <properties>\n"
+ " <profile.jdk>activated</profile.jdk>\n"
+ " </properties>\n"
+ " <repositories>\n"
+ " <repository>\n"
+ " <id>profile-repo</id>\n"
+ " <url>https://repo.example.test/profile</url>\n"
+ " </repository>\n"
+ " </repositories>\n"
+ " </profile>\n"
+ " </profiles>\n"
+ "</project>\n";

private Model build(int validationLevel) throws Exception {
ModelBuilder builder = new DefaultModelBuilderFactory().newInstance();

Properties systemProperties = new Properties();
systemProperties.putAll(System.getProperties());
systemProperties.setProperty("some.dir", System.getProperty("java.io.tmpdir"));
systemProperties.setProperty("some.gating.property", "true");

DefaultModelBuildingRequest request = new DefaultModelBuildingRequest();
request.setModelSource(new StringModelSource(POM));
request.setValidationLevel(validationLevel);
request.setSystemProperties(systemProperties);

return builder.build(request).getEffectiveModel();
}

@Test
void testProjectBuildEvaluatesAllActivators() throws Exception {
Model model = build(ModelBuildingRequest.VALIDATION_LEVEL_STRICT);

assertEquals("activated", model.getProperties().get("profile.file"));
assertEquals("activated", model.getProperties().get("profile.property"));
assertEquals("activated", model.getProperties().get("profile.jdk"));
assertTrue(model.getRepositories().stream().anyMatch(r -> "profile-repo".equals(r.getId())));
}

@Test
void testDependencyPomActivatesOnlyEnvironmentIndependentProfiles() throws Exception {
Model model = build(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);

assertNull(model.getProperties().get("profile.file"));
assertNull(model.getProperties().get("profile.property"));
assertEquals("activated", model.getProperties().get("profile.jdk"));
assertTrue(model.getRepositories().stream().noneMatch(r -> "profile-repo".equals(r.getId())));
}
}
Loading
Loading