Skip to content

Commit e607c8f

Browse files
committed
feat: Add config for using packages api for maven/npm/python issues
This API does not return licensing issues so option is behind config flag BREAKING CHANGE: New configuration options that can result in some loss of functionality
1 parent db4828d commit e607c8f

26 files changed

Lines changed: 833 additions & 27 deletions

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
security-scans:
88
resource_class: small
99
docker:
10-
- image: cimg/openjdk:17.0
10+
- image: cimg/openjdk:21.0
1111
steps:
1212
- checkout
1313
- prodsec/security_scans:

core/src/main/java/io/snyk/plugins/artifactory/PropertyLoader.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import java.util.Properties;
99

1010
import static java.lang.String.format;
11-
import static java.nio.charset.StandardCharsets.UTF_8;
1211

1312
final class PropertyLoader {
1413

@@ -53,6 +52,6 @@ static String loadPluginVersion(@Nonnull File pluginsDirectory) throws IOExcepti
5352
throw new IOException(format(FILE_NOT_FOUND_ERROR_MESSAGE, pluginVersionFile.getAbsolutePath()));
5453
}
5554

56-
return new String(Files.readAllBytes(pluginVersionFile.toPath()), UTF_8);
55+
return Files.readString(pluginVersionFile.toPath());
5756
}
5857
}

core/src/main/java/io/snyk/plugins/artifactory/SnykPlugin.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ public SnykPlugin(@Nonnull Repositories repositories, File pluginsDirectory) {
5252
validateConfiguration();
5353

5454
LOG.info("Creating api client and modules...");
55-
LOG.info("BaseURL:" + configurationModule.getPropertyOrDefault(API_URL));
56-
LOG.info("Organization:" + configurationModule.getPropertyOrDefault(API_ORGANIZATION));
55+
LOG.info("BaseURL: {}", configurationModule.getPropertyOrDefault(API_URL));
56+
LOG.info("Organization: {}", configurationModule.getPropertyOrDefault(API_ORGANIZATION));
5757
String token = configurationModule.getPropertyOrDefault(API_TOKEN);
5858
if (null != token && token.length() > 4) {
5959
token = token.substring(0, 4) + "...";
6060
} else {
6161
token = "no token configured";
6262
}
63-
LOG.debug("Token:" + token);
63+
LOG.debug("Token: {}", token);
6464
final SnykClient snykClient = createSnykClient(configurationModule, pluginVersion);
6565

6666
auditModule = new AuditModule();
@@ -191,8 +191,8 @@ private SnykClient createSnykClient(@Nonnull ConfigurationModule configurationMo
191191
.build();
192192

193193
LOG.debug("about to log config...");
194-
LOG.debug("config.httpProxyHost: " + config.httpProxyHost);
195-
LOG.debug("config.httpProxyPort: " + config.httpProxyPort);
194+
LOG.debug("config.httpProxyHost: {}", config.httpProxyHost);
195+
LOG.debug("config.httpProxyPort: {}", config.httpProxyPort);
196196

197197
final SnykClient snykClient = new SnykClient(config);
198198

core/src/main/java/io/snyk/plugins/artifactory/configuration/PluginConfiguration.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ public enum PluginConfiguration implements Configuration {
88
API_SSL_CERTIFICATE_PATH("snyk.api.sslCertificatePath", ""),
99
API_TRUST_ALL_CERTIFICATES("snyk.api.trustAllCertificates", "false"),
1010
API_TIMEOUT("snyk.api.timeout", "60000"),
11+
/* API_REST_ENABLED is used to determine whether to use the REST API or the legacy API
12+
* for the Maven, Npm, and Python scanners.
13+
* This results in different behavior:
14+
* - the REST API does not provide licensing information, but does provide vulnerability information
15+
* - the legacy API provides both licensing and vulnerability information
16+
* This does not affect the RubyGems, Nuget, and CocoaPods scanners, which only use the REST API.
17+
*/
18+
API_REST_ENABLED("snyk.api.rest.enabled", "false"),
1119

1220
HTTP_PROXY_HOST("snyk.http.proxyHost", ""),
1321
HTTP_PROXY_PORT("snyk.http.proxyPort", "80"),

core/src/main/java/io/snyk/plugins/artifactory/scanner/ScannerModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ private ArtifactProperties properties(RepoPath repoPath) {
7676

7777
private @NotNull Optional<MonitoredArtifact> runTest(RepoPath repoPath) {
7878
return ecosystemResolver.getFor(repoPath)
79-
.flatMap(ecosystem -> scannerResolver.getFor(ecosystem))
79+
.flatMap(scannerResolver::getFor)
8080
.map(scanner -> runTestWith(scanner, repoPath));
8181
}
8282

core/src/main/java/io/snyk/plugins/artifactory/scanner/ScannerResolver.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@
44
import io.snyk.plugins.artifactory.configuration.PluginConfiguration;
55
import io.snyk.plugins.artifactory.ecosystem.Ecosystem;
66
import io.snyk.plugins.artifactory.scanner.cocoapods.CocoapodsScanner;
7+
import io.snyk.plugins.artifactory.scanner.maven.MavenPurlScanner;
8+
import io.snyk.plugins.artifactory.scanner.maven.MavenScanner;
9+
import io.snyk.plugins.artifactory.scanner.npm.NpmPurlScanner;
10+
import io.snyk.plugins.artifactory.scanner.npm.NpmScanner;
711
import io.snyk.plugins.artifactory.scanner.nuget.NugetScanner;
812
import io.snyk.plugins.artifactory.scanner.purl.PurlScanner;
13+
import io.snyk.plugins.artifactory.scanner.python.PythonPurlScanner;
14+
import io.snyk.plugins.artifactory.scanner.python.PythonScanner;
915
import io.snyk.plugins.artifactory.scanner.rubygems.RubyGemsScanner;
1016
import io.snyk.sdk.api.SnykClient;
1117
import org.slf4j.Logger;
@@ -17,6 +23,7 @@
1723
import java.util.function.Function;
1824

1925
import static io.snyk.plugins.artifactory.configuration.PluginConfiguration.API_ORGANIZATION;
26+
import static io.snyk.plugins.artifactory.configuration.PluginConfiguration.API_REST_ENABLED;
2027

2128
public class ScannerResolver {
2229
private static final Logger LOG = LoggerFactory.getLogger(ScannerResolver.class);
@@ -52,13 +59,24 @@ public Optional<PackageScanner> getFor(Ecosystem ecosystem) {
5259
public static ScannerResolver setup(ConfigurationModule configurationModule, SnykClient snykClient) {
5360
String orgId = configurationModule.getProperty(API_ORGANIZATION);
5461
PurlScanner purlScanner = new PurlScanner(snykClient, orgId);
55-
return new ScannerResolver(configurationModule::getPropertyOrDefault)
56-
.register(Ecosystem.MAVEN, new MavenScanner(configurationModule, snykClient))
57-
.register(Ecosystem.NPM, new NpmScanner(configurationModule, snykClient))
58-
.register(Ecosystem.PYPI, new PythonScanner(configurationModule, snykClient))
62+
var scannerResolver = new ScannerResolver(configurationModule::getPropertyOrDefault);
63+
64+
if (Boolean.parseBoolean(configurationModule.getPropertyOrDefault(API_REST_ENABLED))) {
65+
scannerResolver
66+
.register(Ecosystem.MAVEN, new MavenPurlScanner(purlScanner))
67+
.register(Ecosystem.NPM, new NpmPurlScanner(purlScanner))
68+
.register(Ecosystem.PYPI, new PythonPurlScanner(purlScanner));
69+
} else {
70+
scannerResolver
71+
.register(Ecosystem.MAVEN, new MavenScanner(configurationModule, snykClient))
72+
.register(Ecosystem.NPM, new NpmScanner(configurationModule, snykClient))
73+
.register(Ecosystem.PYPI, new PythonScanner(configurationModule, snykClient));
74+
}
75+
scannerResolver
5976
.register(Ecosystem.RUBYGEMS, new RubyGemsScanner(purlScanner))
6077
.register(Ecosystem.NUGET, new NugetScanner(purlScanner))
6178
.register(Ecosystem.COCOAPODS, new CocoapodsScanner(purlScanner))
6279
;
80+
return scannerResolver;
6381
}
6482
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.snyk.plugins.artifactory.scanner.maven;
2+
3+
import org.artifactory.fs.FileLayoutInfo;
4+
import org.slf4j.Logger;
5+
6+
import java.util.Optional;
7+
8+
import static org.slf4j.LoggerFactory.getLogger;
9+
10+
public class MavenPackage {
11+
private static final Logger LOG = getLogger(MavenPackage.class);
12+
private final String groupID;
13+
private final String artifactID;
14+
private final String version;
15+
16+
public MavenPackage(String groupID, String artifactID, String version) {
17+
this.groupID = groupID;
18+
this.artifactID = artifactID;
19+
this.version = version;
20+
}
21+
22+
public String getGroupID() {
23+
return groupID;
24+
}
25+
26+
public String getArtifactID() {
27+
return artifactID;
28+
}
29+
30+
public String getName() {
31+
return groupID + "/" + artifactID;
32+
}
33+
34+
public String getVersion() {
35+
return version;
36+
}
37+
38+
public static Optional<MavenPackage> parse(FileLayoutInfo fileLayoutInfo) {
39+
String groupID = fileLayoutInfo.getOrganization();
40+
String artifactID = fileLayoutInfo.getModule();
41+
String version = fileLayoutInfo.getBaseRevision();
42+
43+
if (groupID == null || artifactID == null || version == null) {
44+
LOG.warn("Maven package details not provided in FileLayoutInfo");
45+
return Optional.empty();
46+
}
47+
48+
return Optional.of(new MavenPackage(groupID, artifactID, version));
49+
}
50+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.snyk.plugins.artifactory.scanner.maven;
2+
3+
import io.snyk.plugins.artifactory.exception.CannotScanException;
4+
import io.snyk.plugins.artifactory.model.TestResult;
5+
import io.snyk.plugins.artifactory.scanner.PackageScanner;
6+
import io.snyk.plugins.artifactory.scanner.SnykDetailsUrl;
7+
import io.snyk.plugins.artifactory.scanner.purl.PurlScanner;
8+
import org.artifactory.fs.FileLayoutInfo;
9+
import org.artifactory.repo.RepoPath;
10+
import org.slf4j.Logger;
11+
12+
import static org.slf4j.LoggerFactory.getLogger;
13+
14+
public class MavenPurlScanner implements PackageScanner {
15+
16+
private static final Logger LOG = getLogger(MavenPurlScanner.class);
17+
private final PurlScanner purlScanner;
18+
19+
public MavenPurlScanner(PurlScanner purlScanner) {
20+
this.purlScanner = purlScanner;
21+
}
22+
23+
@Override
24+
public TestResult scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath) {
25+
LOG.debug("Maven: repoPath.getName() {}", repoPath.getName());
26+
27+
MavenPackage pckg = MavenPackage.parse(fileLayoutInfo)
28+
.orElseThrow(() -> new CannotScanException("Maven package details not provided"));
29+
30+
String purl = "pkg:maven/" + pckg.getName() + "@" + pckg.getVersion();
31+
32+
String packageDetailsUrl = getArtifactDetailsURL(pckg.getGroupID(), pckg.getArtifactID(), pckg.getVersion());
33+
34+
return purlScanner.scan(purl, packageDetailsUrl);
35+
}
36+
37+
public static String getArtifactDetailsURL(String groupID, String artifactID, String artifactVersion) {
38+
return SnykDetailsUrl.create("maven", groupID + ":" + artifactID, artifactVersion).toString();
39+
}
40+
}
41+

core/src/main/java/io/snyk/plugins/artifactory/scanner/MavenScanner.java renamed to core/src/main/java/io/snyk/plugins/artifactory/scanner/maven/MavenScanner.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
package io.snyk.plugins.artifactory.scanner;
1+
package io.snyk.plugins.artifactory.scanner.maven;
22

33
import io.snyk.plugins.artifactory.configuration.ConfigurationModule;
44
import io.snyk.plugins.artifactory.exception.CannotScanException;
55
import io.snyk.plugins.artifactory.exception.SnykAPIFailureException;
6+
import io.snyk.plugins.artifactory.scanner.PackageScanner;
7+
import io.snyk.plugins.artifactory.scanner.SnykDetailsUrl;
8+
import io.snyk.plugins.artifactory.scanner.TestResultConverter;
69
import io.snyk.sdk.api.SnykClient;
710
import io.snyk.sdk.api.SnykResult;
811
import io.snyk.sdk.model.TestResult;
@@ -17,14 +20,14 @@
1720
import static java.nio.charset.StandardCharsets.UTF_8;
1821
import static org.slf4j.LoggerFactory.getLogger;
1922

20-
class MavenScanner implements PackageScanner {
23+
public class MavenScanner implements PackageScanner {
2124

2225
private static final Logger LOG = getLogger(MavenScanner.class);
2326

2427
private final ConfigurationModule configurationModule;
2528
private final SnykClient snykClient;
2629

27-
MavenScanner(ConfigurationModule configurationModule, SnykClient snykClient) {
30+
public MavenScanner(ConfigurationModule configurationModule, SnykClient snykClient) {
2831
this.configurationModule = configurationModule;
2932
this.snykClient = snykClient;
3033
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.snyk.plugins.artifactory.scanner.npm;
2+
3+
import org.slf4j.Logger;
4+
5+
import java.util.Optional;
6+
import java.util.regex.Matcher;
7+
import java.util.regex.Pattern;
8+
9+
import static org.slf4j.LoggerFactory.getLogger;
10+
11+
public class NpmPackage {
12+
private static final Logger LOG = getLogger(NpmPackage.class);
13+
private final String name;
14+
private final String version;
15+
16+
public NpmPackage(String name, String version) {
17+
this.name = name;
18+
this.version = version;
19+
}
20+
21+
public String getName() {
22+
return name;
23+
}
24+
25+
public String getVersion() {
26+
return version;
27+
}
28+
29+
public static Optional<NpmPackage> parse(String repoPath) {
30+
if (repoPath == null) {
31+
LOG.warn("Unexpected package path: null");
32+
return Optional.empty();
33+
}
34+
35+
// Pattern matches the full repo path: npm:lodash/-/lodash-4.17.15.tgz
36+
// Extracts package name before /-/ and version after last hyphen before .tgz
37+
Pattern pattern = Pattern.compile("^(?:.+:)?(?<packageName>.+)/-/.+-(?<packageVersion>\\d+\\.\\d+\\.\\d+.*)\\.tgz$");
38+
Matcher matcher = pattern.matcher(repoPath);
39+
40+
if (!matcher.matches()) {
41+
LOG.warn("Unexpected Npm package path: {}", repoPath);
42+
return Optional.empty();
43+
}
44+
45+
return Optional.of(new NpmPackage(
46+
matcher.group("packageName"),
47+
matcher.group("packageVersion")
48+
));
49+
}
50+
}

0 commit comments

Comments
 (0)