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

feat: Add support for github component sources #3112

Merged
merged 1 commit into from
Nov 13, 2023
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
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
<lib.signpost-core.version>2.1.1</lib.signpost-core.version>
<lib.httpclient.version>4.5.14</lib.httpclient.version>
<lib.log4j-over-slf4j.version>2.0.9</lib.log4j-over-slf4j.version>
<lib.org-kohsuke-github-api.version>1.316</lib.org-kohsuke-github-api.version>
<!-- JDBC Drivers -->
<lib.jdbc-driver.mssql.version>12.4.2.jre11</lib.jdbc-driver.mssql.version>
<!-- Upgrading to 8.0.33 as https://github.com/datanucleus/datanucleus-rdbms/issues/446 is resolved! -->
Expand Down Expand Up @@ -352,6 +353,12 @@
<version>${lib.log4j-over-slf4j.version}</version>
</dependency>

<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>${lib.org-kohsuke-github-api.version}</version>
</dependency>

<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/dependencytrack/model/RepositoryType.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public enum RepositoryType {
COMPOSER,
CARGO,
GO_MODULES,
GITHUB,
UNSUPPORTED;

/**
Expand Down Expand Up @@ -67,6 +68,8 @@ public static RepositoryType resolve(PackageURL packageURL) {
return CARGO;
} else if (PackageURL.StandardTypes.GOLANG.equals(type)) {
return GO_MODULES;
} else if (PackageURL.StandardTypes.GITHUB.equals(type)) {
return GITHUB;
}
return UNSUPPORTED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ private void loadDefaultRepositories() {
qm.createRepository(RepositoryType.COMPOSER, "packagist", "https://repo.packagist.org/", true, false);
qm.createRepository(RepositoryType.CARGO, "crates.io", "https://crates.io", true, false);
qm.createRepository(RepositoryType.GO_MODULES, "proxy.golang.org", "https://proxy.golang.org", true, false);
qm.createRepository(RepositoryType.GITHUB, "github.com", "https://github.com", true, false);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* This file is part of Dependency-Track.
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package org.dependencytrack.tasks.repositories;

import alpine.common.logging.Logger;
import com.github.packageurl.PackageURL;
import org.dependencytrack.exception.MetaAnalyzerException;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.RepositoryType;

import java.util.regex.Pattern;
import java.io.IOException;

import org.kohsuke.github.GitHub;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHBranch;
import org.kohsuke.github.GHRelease;
import org.kohsuke.github.GHCommit;

/**
* An IMetaAnalyzer implementation that supports GitHub via the api
*
* @author Jadyn Jaeger
* @since 4.9.0
*/
public class GithubMetaAnalyzer extends AbstractMetaAnalyzer {

private static final Logger LOGGER = Logger.getLogger(GithubMetaAnalyzer.class);

private static final int VERSION_TYPE_RELEASE = 1;
private static final int VERSION_TYPE_COMMIT = 2;
private static final String VERSION_TYPE_PATTERN = "[a-f,0-9]{6,40}";
private static final String REPOSITORY_DEFAULT_URL = "https://github.com";
private String REPOSITORY_URL = "";
private String REPOSITORY_USER = "";
private String REPOSITORY_PASSWORD = "";

GithubMetaAnalyzer() {
this.REPOSITORY_URL = REPOSITORY_DEFAULT_URL;
}
/**
* {@inheritDoc}
*/
@Override
public void setRepositoryBaseUrl(String baseUrl) {
this.REPOSITORY_URL = baseUrl;
}

/**
* {@inheritDoc}
*/
@Override
public void setRepositoryUsernameAndPassword(String username, String password) {
this.REPOSITORY_USER = username;
this.REPOSITORY_PASSWORD = password;
}

/**
* {@inheritDoc}
*/
public boolean isApplicable(final Component component) {
return component.getPurl() != null && PackageURL.StandardTypes.GITHUB.equals(component.getPurl().getType());
}

/**
* {@inheritDoc}
*/
public RepositoryType supportedRepositoryType() {
return RepositoryType.GITHUB;
}

/**
* {@inheritDoc}
*/
public MetaModel analyze(final Component component) {
final MetaModel meta = new MetaModel(component);
if (component.getPurl() != null) {
try{
GitHub github;
if (!REPOSITORY_USER.isEmpty() && !REPOSITORY_PASSWORD.isEmpty()) {
github = GitHub.connect(REPOSITORY_USER, REPOSITORY_PASSWORD);
} else if (REPOSITORY_USER.isEmpty() && !REPOSITORY_PASSWORD.isEmpty()){
github = GitHub.connectUsingOAuth(REPOSITORY_URL, REPOSITORY_PASSWORD);
} else {
github = GitHub.connectAnonymously();
}

Pattern version_pattern = Pattern.compile(VERSION_TYPE_PATTERN);
int version_type = 0;
if (version_pattern.matcher(component.getPurl().getVersion()).find()){
LOGGER.debug("Version is commit");
version_type = VERSION_TYPE_COMMIT;
} else {
LOGGER.debug("Version is release");
version_type = VERSION_TYPE_RELEASE;
}

GHRepository repository = github.getRepository(String.format("%s/%s", component.getPurl().getNamespace(), component.getPurl().getName()));
LOGGER.debug(String.format("Repos is at %s", repository.getUrl()));
if (version_type == VERSION_TYPE_RELEASE){
GHRelease latest_release = repository.getLatestRelease();
GHRelease current_release = repository.getReleaseByTagName(component.getPurl().getVersion());
meta.setLatestVersion(latest_release.getTagName());
LOGGER.debug(String.format("Latest version: %s", meta.getLatestVersion()));
meta.setPublishedTimestamp(current_release.getPublished_at());
LOGGER.debug(String.format("Current version published at: %s", meta.getPublishedTimestamp()));
} else if (version_type == VERSION_TYPE_COMMIT){
GHBranch default_branch = repository.getBranch(repository.getDefaultBranch());
GHCommit latest_release = repository.getCommit(default_branch.getSHA1());
GHCommit current_release = repository.getCommit(component.getPurl().getVersion());
meta.setLatestVersion(latest_release.getSHA1());
LOGGER.debug(String.format("Latest version: %s", meta.getLatestVersion()));
meta.setPublishedTimestamp(current_release.getCommitDate());
LOGGER.debug(String.format("Current version published at: %s", meta.getPublishedTimestamp()));
}

} catch (IOException ex) {
handleRequestException(LOGGER, ex);
} catch (Exception ex) {
throw new MetaAnalyzerException(ex);
}
}
return meta;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ static IMetaAnalyzer build(Component component) {
if (analyzer.isApplicable(component)) {
return analyzer;
}
} else if (PackageURL.StandardTypes.GITHUB.equals(component.getPurl().getType())) {
IMetaAnalyzer analyzer = new GithubMetaAnalyzer();
if (analyzer.isApplicable(component)) {
return analyzer;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public void testLoadDefaultRepositories() throws Exception {
Method method = generator.getClass().getDeclaredMethod("loadDefaultRepositories");
method.setAccessible(true);
method.invoke(generator);
Assert.assertEquals(14, qm.getAllRepositories().size());
Assert.assertEquals(15, qm.getAllRepositories().size());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ public void getRepositoriesTest() {
.header(X_API_KEY, apiKey)
.get(Response.class);
Assert.assertEquals(200, response.getStatus(), 0);
Assert.assertEquals(String.valueOf(14), response.getHeaderString(TOTAL_COUNT_HEADER));
Assert.assertEquals(String.valueOf(15), response.getHeaderString(TOTAL_COUNT_HEADER));
JsonArray json = parseJsonArray(response);
Assert.assertNotNull(json);
Assert.assertEquals(14, json.size());
Assert.assertEquals(15, json.size());
for (int i=0; i<json.size(); i++) {
Assert.assertNotNull(json.getJsonObject(i).getString("type"));
Assert.assertNotNull(json.getJsonObject(i).getString("identifier"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* This file is part of Dependency-Track.
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package org.dependencytrack.tasks.repositories;

import com.github.packageurl.PackageURL;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.RepositoryType;
import org.junit.Assert;
import org.junit.Test;

import java.util.regex.Pattern;

public class GitHubMetaAnalyzerTest {

private static final String VERSION_TYPE_PATTERN = "[a-f,0-9]{6,40}";
@Test
public void testAnalyzerRelease() throws Exception {
final var component = new Component();
component.setPurl(new PackageURL("pkg:github/CycloneDX/[email protected]"));

final var analyzer = new GithubMetaAnalyzer();
Assert.assertTrue(analyzer.isApplicable(component));
Assert.assertEquals(RepositoryType.GITHUB, analyzer.supportedRepositoryType());

MetaModel metaModel = analyzer.analyze(component);
Assert.assertNotNull(metaModel.getLatestVersion());
Assert.assertTrue(metaModel.getLatestVersion().startsWith("v"));
Assert.assertNotNull(metaModel.getPublishedTimestamp());
}
@Test
public void testAnalyzerLongCommit() throws Exception{
final var component = new Component();
component.setPurl(new PackageURL("pkg:github/CycloneDX/cdxgen@4359dee1b7bd29ee25bc78e358a1254a0277ee96"));
Pattern version_pattern = Pattern.compile(VERSION_TYPE_PATTERN);

final var analyzer = new GithubMetaAnalyzer();
Assert.assertTrue(analyzer.isApplicable(component));
Assert.assertEquals(RepositoryType.GITHUB, analyzer.supportedRepositoryType());

MetaModel metaModel = analyzer.analyze(component);
Assert.assertNotNull(metaModel.getLatestVersion());
Assert.assertTrue(version_pattern.matcher(metaModel.getLatestVersion()).find());
Assert.assertNotNull(metaModel.getPublishedTimestamp());
}

@Test
public void testAnalyzerShortCommit() throws Exception{
final var component = new Component();
component.setPurl(new PackageURL("pkg:github/CycloneDX/cdxgen@4359dee"));
Pattern version_pattern = Pattern.compile(VERSION_TYPE_PATTERN);

final var analyzer = new GithubMetaAnalyzer();
Assert.assertTrue(analyzer.isApplicable(component));
Assert.assertEquals(RepositoryType.GITHUB, analyzer.supportedRepositoryType());

MetaModel metaModel = analyzer.analyze(component);
Assert.assertNotNull(metaModel.getLatestVersion());
Assert.assertTrue(version_pattern.matcher(metaModel.getLatestVersion()).find());
Assert.assertNotNull(metaModel.getPublishedTimestamp());
}
}