Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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 @@ -816,5 +816,40 @@ public final class Constants {
*/
public static final String MAVEN_MODEL_PROCESSOR_REFERENCE_TYPE_PREFIX = "maven.model.processor.referenceType.";

/**
* User property for allowing {@code system} scope and {@code systemPath} in dependency
* management imported from repository-resolved POMs (BOMs).
* <ul>
* <li>When set to <code>false</code> (default), dependency management imported from a
* repository-resolved POM may not declare {@code system} scope or a {@code systemPath};
* such entries are dropped with a warning.</li>
* <li>When set to <code>true</code>, such entries are imported as in previous Maven
* versions, with a warning.</li>
* </ul>
* Dependency management imported from the local reactor is not affected by this property.
*
* @since 4.1.0
*/
@Config(type = "java.lang.Boolean", defaultValue = "false")
public static final String MAVEN_REPOSITORY_DEPENDENCY_MANAGEMENT_ALLOW_SYSTEM_SCOPE =
"maven.repository.dependencyManagement.allowSystemScope";

/**
* User property for opting back into the previous behavior of interpolating
* repository-resolved models (dependencies and parents) against the full set of
* session properties (system, environment and CLI).
* <ul>
* <li>When set to <code>false</code> (default), models resolved from a
* repository are interpolated only against their own {@code <properties>},
* preventing property leaking from the requesting build into transitive POMs.</li>
* <li>When set to <code>true</code>, full interpolation is applied as in
* previous Maven versions.</li>
* </ul>
*
* @since 4.1.0
*/
@Config(type = "java.lang.Boolean", defaultValue = "false")
public static final String MAVEN_MODEL_DEPENDENCY_INTERPOLATION_FULL = "maven.model.dependencyInterpolation.full";

private Constants() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ protected ModelBuildingResult build(ModelBuildingRequest request, Collection<Str
Collection<String> parentIds = new LinkedHashSet<>();
List<ModelData> lineage = new ArrayList<>();

// Models built for dependency resolution (a dependency POM, one of its parents, or an
// imported BOM) are read at ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL rather than
// the default STRICT level used for the project being built.
boolean externalModel = isExternalModelBuildingRequest(request);

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

Expand All @@ -309,8 +314,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 @@ -320,7 +327,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 @@ -495,6 +506,44 @@ void performFor(String value, String locationKey, Consumer<String> mutator) {
return interpolatedActivations;
}

/**
* Determines whether the given request builds a model for dependency resolution, 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 are issued with
* {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL}, see for instance
* {@code DefaultArtifactDescriptorReader#loadPom}.
*/
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 @@ -50,6 +50,13 @@
*/
@Deprecated(since = "4.0.0")
public abstract class AbstractStringBasedModelInterpolator implements ModelInterpolator {

/**
* Local mirror of {@code org.apache.maven.api.Constants.MAVEN_MODEL_DEPENDENCY_INTERPOLATION_FULL}.
* This compat module does not depend on {@code maven-api-core}, so the value is duplicated here.
*/
private 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 @@ -156,25 +163,70 @@ 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()));
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);

valueSources.add(new AbstractValueSource(false) {
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 config.getSystemProperties().getProperty("env." + expression);
return isSafeExternalExpression(expression) ? source.getValue(expression) : null;
}
});

valueSources.add(modelValueSource2);
};
}

return valueSources;
/**
* 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(
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 ExternalModelProfileActivationTest {

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