Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧 Third party repository detection probe #323

Closed
Closed
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
8d74f5c
Started with writing test cases for the probe
Jagrutiti Jun 1, 2023
7d62076
Removing unwanted details from test pom file.
Jagrutiti Jun 2, 2023
4286ff4
Adding dummy to pom test files
Jagrutiti Jun 2, 2023
f412657
Adding dummy values to pom test files
Jagrutiti Jun 2, 2023
537d68a
Restoring valuable test data
Jagrutiti Jun 2, 2023
491d16d
Updating the code and test cases
Jagrutiti Jun 3, 2023
3f593a4
Removing unused imports
Jagrutiti Jun 3, 2023
365db47
Fixing code
Jagrutiti Jun 3, 2023
3bb27f5
Undid irrelevant change
Jagrutiti Jun 3, 2023
99a1379
Apply suggestions from code review
Jagrutiti Jun 3, 2023
e050c24
Restoring .gitignore
Jagrutiti Jun 3, 2023
d830b63
Merge branch 'third-party-repository-detection' of https://github.com…
Jagrutiti Jun 3, 2023
fda43ae
Updated the code and the test case
Jagrutiti Jun 3, 2023
c5c3530
Updating the test case
Jagrutiti Jun 3, 2023
5dfd1ed
updating the code and revamping the test cases
Jagrutiti Jun 4, 2023
9b246d4
Updating the test cases
Jagrutiti Jun 4, 2023
00340f8
Updated the success to be parameterized
Jagrutiti Jun 4, 2023
a39e24e
Apply suggestions from code review
Jagrutiti Jun 4, 2023
008065d
Fixed the test cases
Jagrutiti Jun 4, 2023
c565cb4
Updated the test cases
Jagrutiti Jun 4, 2023
ec9b627
Merge branch 'main' into third-party-repository-detection
Jagrutiti Jun 5, 2023
d30ec44
Merge branch 'main' of https://github.com/Jagrutiti/plugin-health-sco…
Jagrutiti Jun 5, 2023
643f599
Merge branch 'third-party-repository-detection' of https://github.com…
Jagrutiti Jun 5, 2023
2cb3048
Updating code and tests to check for pluginRespositories
Jagrutiti Jun 5, 2023
b1b5257
Removing unused import
Jagrutiti Jun 5, 2023
d812c81
FIxing minor syntaxes
Jagrutiti Jun 5, 2023
355d923
Fixed minor syntaxes
Jagrutiti Jun 5, 2023
6d7b31c
Apply suggestions from code review
Jagrutiti Jun 6, 2023
1b3ad71
Apply suggestions from code review
Jagrutiti Jun 6, 2023
dbdfe6f
Adding pom test files
Jagrutiti Jun 6, 2023
53fb134
Apply suggestions from code review
Jagrutiti Jun 6, 2023
ed123c5
Updating code for the tests to fail when no repositories detected
Jagrutiti Jun 6, 2023
e273093
Merge branch 'third-party-repository-detection' of https://github.com…
Jagrutiti Jun 6, 2023
9f7ab30
Updated test case and test files
Jagrutiti Jun 6, 2023
eba72e7
Upating test case and test pom files
Jagrutiti Jun 7, 2023
d5f4070
Fixing checkstyle issues
Jagrutiti Jun 7, 2023
4833c9b
Modifying test cases and code
Jagrutiti Jun 10, 2023
24c911b
Merge branch 'main' into third-party-repository-detection
Jagrutiti Jun 10, 2023
039362e
Undoing editorconfig change
Jagrutiti Jun 10, 2023
2f1638e
Adding additional test files
Jagrutiti Jun 10, 2023
5560353
Fixed code and checkstyle
Jagrutiti Jun 10, 2023
ce97eb0
Removing the effective-pom file
Jagrutiti Jun 13, 2023
148d4dd
Added spotbugs fixes
Jagrutiti Jun 13, 2023
dbe70d5
Added spotbugs fixes
Jagrutiti Jun 13, 2023
276968c
Merge branch 'main' into third-party-repository-detection
Jagrutiti Jun 13, 2023
0be6114
Added spotbugs fixes
Jagrutiti Jun 13, 2023
9343f56
Merge branch 'third-party-repository-detection' of https://github.com…
Jagrutiti Jun 13, 2023
9d43283
Removing unused imports
Jagrutiti Jun 13, 2023
6d42558
Apply suggestions from code review
Jagrutiti Jun 14, 2023
3659137
Fixed missing symbols error
Jagrutiti Jun 14, 2023
58ba0b1
Fixing checkstyle issues
Jagrutiti Jun 14, 2023
da55c4d
Merge branch 'main' into third-party-repository-detection
Jagrutiti Jun 14, 2023
fc8ec3b
Adding code for effective-pom generation and updated the test cases:
Jagrutiti Sep 3, 2023
d77baf1
Adding comments
Jagrutiti Sep 3, 2023
9b23f07
Merge branch 'main' into third-party-repository-detection
Jagrutiti Sep 3, 2023
85c10f3
Merge branch 'main' into third-party-repository-detection
Jagrutiti Sep 7, 2023
1fa7c2b
Fixed merge issues
Jagrutiti Sep 7, 2023
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
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,9 @@
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
</dependency>
</dependencies>
</project>
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package io.jenkins.pluginhealth.scoring.probes;

import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.Optional;

Expand All @@ -49,11 +50,15 @@ public abstract class Probe {
* @return the result of the analyze in a {@link ProbeResult}
*/
public final ProbeResult apply(Plugin plugin, ProbeContext context) {
if (shouldBeExecuted(plugin, context)) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Running {} on {}", this.key(), plugin.getName());
try {
if (shouldBeExecuted(plugin, context)) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Running {} on {}", this.key(), plugin.getName());
}
return doApply(plugin, context);
}
return doApply(plugin, context);
} catch (IOException e) {
LOGGER.error("File Reading exception {}", e);
}
return ProbeResult.error(key(), key() + " does not meet the criteria to be executed on " + plugin.getName());
}
Expand Down Expand Up @@ -104,7 +109,7 @@ private boolean shouldBeExecuted(Plugin plugin, ProbeContext context) {
* @param context holder of information passed across the probes executed on a single plugin
* @return a ProbeResult representing the result of the analysis
*/
protected abstract ProbeResult doApply(Plugin plugin, ProbeContext context);
protected abstract ProbeResult doApply(Plugin plugin, ProbeContext context) throws IOException;
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved

/**
* List of probe key to be present in the {@link Plugin#details} map and to be {@link ResultStatus#SUCCESS} in
Expand Down
alecharp marked this conversation as resolved.
Show resolved Hide resolved
alecharp marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.jenkins.pluginhealth.scoring.probes;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

import io.jenkins.pluginhealth.scoring.model.Plugin;
import io.jenkins.pluginhealth.scoring.model.ProbeResult;

import org.apache.maven.model.Model;
import org.apache.maven.model.Repository;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(value = ThirdPartyRepositoryDetectionProbe.ORDER)
public class ThirdPartyRepositoryDetectionProbe extends Probe {
private static final Logger LOGGER = LoggerFactory.getLogger(ThirdPartyRepositoryDetectionProbe.class);
public static final int ORDER = SCMLinkValidationProbe.ORDER + 100;
public static final String KEY = "third-party-repository-detection-probe";
final String hostName = "https://repo.jenkins-ci.org";
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
// final String parentPom = "https://raw.githubusercontent.com/jenkinsci/plugin-pom/master/pom.xml";
final String parentPom = "https://github.com/jenkinsci/plugin-pom/blob/master/pom.xml";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to use this in the beginning but now I am not.

For test cases, I do not need to. But I am not sure about real-world scenarios.

I was unable to read the pom from the URL in MavenXpp3Reader

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is what we want here.


@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
MavenXpp3Reader mavenReader = new MavenXpp3Reader();
Set<Repository> allRepositories = new HashSet<>();

try {
InputStream inputStream = new FileInputStream(context.getScmRepository() + "/pom.xml");
Reader reader = new InputStreamReader(inputStream, "UTF-8");
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
Model model = mavenReader.read(reader);
allRepositories.addAll(model.getRepositories());
allRepositories.addAll(model.getPluginRepositories());

if (!model.getParent().getRelativePath().isBlank()) {
Model parentPomModel = parsePomFromUrl(model.getParent().getRelativePath());
allRepositories.addAll(parentPomModel.getRepositories());
allRepositories.addAll(parentPomModel.getPluginRepositories());
}
for (Repository repository : allRepositories) {
if (!repository.getUrl().startsWith(hostName)) {
return ProbeResult.failure(KEY, "Third party repositories detected in the plugin");
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure you can simplify that by filtering the model repositories before adding them to the set.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On what parameters you want me to filter them?

} catch (FileNotFoundException e) {
LOGGER.error("File not found at {}", plugin.getName());
return ProbeResult.error(KEY, e.getMessage());
} catch (XmlPullParserException e) {
LOGGER.error("Pom file could not be parsed at {}", plugin.getName());
return ProbeResult.error(KEY, e.getMessage());
} catch (IOException e) {
LOGGER.error("File reading exception at {}", plugin.getName());
return ProbeResult.error(KEY, e.getMessage());

}
return allRepositories.size() > 0 ? ProbeResult.success(KEY, "The plugin has no third party repositories")
: ProbeResult.failure(KEY, "No repositories detected");
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public String key() {
return KEY;
}

@Override
public String getDescription() {
return "Detects third-party repositories in a plugin.";
}

@Override
public String[] getProbeResultRequirement() {
return new String[] { SCMLinkValidationProbe.KEY};
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
}

public Model parsePomFromUrl(String pomUrl) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this because what it happening if someone puts an url pointing to a fake pom.xml which run random code?

Copy link
Member Author

@Jagrutiti Jagrutiti Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean what if there is a fake parent path in the child pom.xml ?

Copy link
Member Author

@Jagrutiti Jagrutiti Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do I validate the path? What is the correct way to resolve this? I am clueless here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing we can do is check the extension of the file. Whether it ends with .xml or the file name is pom.xml.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should rely on the relativePath tag of the parent.
We should look if the plugin has a folder configured (module) and if so, we should check that the parent pom of the repository has a parent to what we expect, that the module has a parent to the root pom file and that none of them have a third party repository.

Model model = null;

try {
if (pomUrl.startsWith(("https"))) {
URL url = new URL(pomUrl);
try (InputStream inputStream = url.openStream()) {
MavenXpp3Reader mavenReader = new MavenXpp3Reader();
model = mavenReader.read(inputStream);
}
}
else {
// for test cases
InputStream inputStream = new FileInputStream(pomUrl);
Reader reader = new InputStreamReader(inputStream, "UTF-8");
model = new MavenXpp3Reader().read(reader);
}
} catch (IOException e) {
LOGGER.error("File could not be found {}", e.getMessage());
} catch (XmlPullParserException e) {
LOGGER.error("Pom file could not be parsed {}", e.getMessage());
}
return model;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package io.jenkins.pluginhealth.scoring.probes;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.stream.Stream;

import io.jenkins.pluginhealth.scoring.model.Plugin;
import io.jenkins.pluginhealth.scoring.model.ProbeResult;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class ThirdPartyRepositoryDetectionProbeTest extends AbstractProbeTest<ThirdPartyRepositoryDetectionProbe> {
@Override
ThirdPartyRepositoryDetectionProbe getSpy() {
return spy(ThirdPartyRepositoryDetectionProbe.class);
}

@Test
void shouldBeExecutedAfterSCMLinkValidationProbeProbe() {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);
final ThirdPartyRepositoryDetectionProbe probe = getSpy();
when(plugin.getName()).thenReturn("foo-bar");
assertThat(probe.apply(plugin, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status")
.isEqualTo(ProbeResult.error(ThirdPartyRepositoryDetectionProbe.KEY, ""));
verify(probe, never()).doApply(plugin, ctx);
}

private static Stream<Arguments> successes() {
return Stream.of(
arguments(
Paths.get("src", "test", "resources", "pom-test-only-correct-path"),
"https://github.com/jenkinsci/test-plugin"
),
arguments(
Paths.get("src", "test", "resources", "pom-test-parent-child-no-third-party-repository-success", "plugin"),
"https://github.com/jenkinsci/test-plugin"
)
);
}

@ParameterizedTest
@MethodSource("successes")
void shouldPassIfNoThirdPartyRepositoriesDetected(Path resourceDirectory, String scm) {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);

when(ctx.getScmRepository()).thenReturn(resourceDirectory);
when(plugin.getDetails()).thenReturn(Map.of(
SCMLinkValidationProbe.KEY, ProbeResult.success(SCMLinkValidationProbe.KEY, "")
));
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
when(plugin.getScm()).thenReturn(scm);

final ThirdPartyRepositoryDetectionProbe probe = getSpy();
assertThat(probe.apply(plugin, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "message", "status")
.isEqualTo(ProbeResult.success(ThirdPartyRepositoryDetectionProbe.KEY, "The plugin has no third party repositories"));
verify(probe).doApply(any(Plugin.class), any(ProbeContext.class));
}

private static Stream<Arguments> failures() {
return Stream.of(
arguments(
Paths.get("src", "test", "resources", "pom-test-both-paths"),
"https://github.com/jenkinsci/test-plugin"
),
arguments(
Paths.get("src", "test", "resources", "pom-test-only-incorrect-path"),
"https://github.com/jenkinsci/test-plugin"
),
arguments(
Paths.get("src", "test", "resources", "pom-test-third-party-probe-in-parent-failure", "plugin"),
"https://github.com/jenkinsci/test-plugin/plugin"
),
arguments(
Paths.get("src", "test", "resources", "pom-test-third-party-probe-in-child-failure", "plugin"),
"https://github.com/jenkinsci/test-plugin/plugin"
)
);
}

@ParameterizedTest
@MethodSource("failures")
void shouldFailIfThirdPartRepositoriesDetected(Path resourceDirectory, String scm) {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);

when(ctx.getScmRepository()).thenReturn(resourceDirectory);
when(plugin.getDetails()).thenReturn(Map.of(
SCMLinkValidationProbe.KEY, ProbeResult.success(SCMLinkValidationProbe.KEY, "")
));
when(plugin.getScm()).thenReturn(scm);

final ThirdPartyRepositoryDetectionProbe probe = getSpy();
assertThat(probe.apply(plugin, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "message", "status")
.isEqualTo(ProbeResult.failure(ThirdPartyRepositoryDetectionProbe.KEY, "Third party repositories detected in the plugin"));
verify(probe).doApply(any(Plugin.class), any(ProbeContext.class));
}

private static Stream<Arguments> failures2() {
return Stream.of(
arguments(
Paths.get("src", "test", "resources", "pom-test-no-repository-tag"),
"https://github.com/jenkinsci/test-plugin"
),
arguments(
Paths.get("src", "test", "resources", "pom-test-parent-child-no-repository-tag-failure", "plugin"),
"https://github.com/jenkinsci/test-plugin"
)
);
}

@ParameterizedTest
@MethodSource("failures2")
void shouldFailWhenNoRepositoriesDetected(Path resourceDirectory, String scm) {
final Plugin plugin = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);

when(ctx.getScmRepository()).thenReturn(resourceDirectory);
when(plugin.getDetails()).thenReturn(Map.of(
SCMLinkValidationProbe.KEY, ProbeResult.success(SCMLinkValidationProbe.KEY, "")
));
when(plugin.getScm()).thenReturn(scm);

final ThirdPartyRepositoryDetectionProbe probe = getSpy();
assertThat(probe.apply(plugin, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "message", "status")
.isEqualTo(ProbeResult.failure(ThirdPartyRepositoryDetectionProbe.KEY, "No repositories detected"));
verify(probe).doApply(any(Plugin.class), any(ProbeContext.class));
}


}
53 changes: 53 additions & 0 deletions core/src/test/resources/pom-test-both-paths/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is a fake pom used for test cases
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.58</version>
<relativePath />
</parent>

<properties>
<changelist>9999-SNAPSHOT</changelist>
<jenkins.version>2.361.4</jenkins.version>
</properties>


<artifactId>not-a-real-publisher</artifactId>
<name>Fake Publisher</name>
<description>For testing purpose</description>
<version>${changelist}</version>
<packaging>hpi</packaging>
<url>https://test.com/test/${project.artifactId}-plugin</url>

<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</repository>
<repository>
<id>releases-repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/releases/</url>
</repository>
<repository>
<id>atlassian-public</id>
<url>https://packages.atlassian.com/mvn/maven-external/</url>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</pluginRepository>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>https://packages.atlassian.com/mvn/maven-external/</url>
</pluginRepository>
</pluginRepositories>
</project>
28 changes: 28 additions & 0 deletions core/src/test/resources/pom-test-no-repository-tag/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is a fake pom used for test cases
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.58</version>
<relativePath />
</parent>

<properties>
<changelist>9999-SNAPSHOT</changelist>
<jenkins.version>2.361.4</jenkins.version>
</properties>


<artifactId>not-a-real-publisher</artifactId>
<name>Fake Publisher</name>
<description>For testing purpose</description>
<version>${changelist}</version>
<packaging>hpi</packaging>
<url>https://test.com/test/${project.artifactId}-plugin</url>

</project>
Loading