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

Port : Fix JDOUserException when multiple licenses match a component's licence #806

Merged
merged 1 commit into from
Jul 29, 2024
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 @@ -26,6 +26,7 @@
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import java.util.List;
import java.util.Map;

final class LicenseQueryManager extends QueryManager implements IQueryManager {

Expand Down Expand Up @@ -91,6 +92,20 @@ public License getLicense(String licenseId) {
return singleResult(query.execute(licenseId));
}

/**
* @since 4.12.0
*/
@Override
public License getLicenseByIdOrName(final String licenseIdOrName) {
final Query<License> query = pm.newQuery(License.class);
query.setFilter("licenseId == :licenseIdOrName || name == :licenseIdOrName");
query.setNamedParameters(Map.of("licenseIdOrName", licenseIdOrName));
query.setOrdering("licenseId asc"); // Ensure result is consistent.
query.setRange(0, 1); // Multiple licenses can have the same name; Pick the first one.
final License license = query.executeUnique();
return license != null ? license : License.UNRESOLVED;
}

/**
* Creates a new License.
* @param license the License object to create
Expand Down Expand Up @@ -160,6 +175,20 @@ public License createCustomLicense(License license, boolean commitIndex) {
return result;
}

/**
* @since 4.12.0
*/
@Override
public License getCustomLicenseByName(final String licenseName) {
final Query<License> query = pm.newQuery(License.class);
query.setFilter("name == :name && customLicense == true");
query.setParameters(licenseName);
query.setOrdering("licenseId asc"); // Ensure result is consistent.
query.setRange(0, 1); // Multiple licenses can have the same name; Pick the first one.
final License license = query.executeUnique();
return license != null ? license : License.UNRESOLVED;
}

/**
* Deletes a license.
* @param license the license to delete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,10 @@ public License getLicense(String licenseId) {
return getLicenseQueryManager().getLicense(licenseId);
}

public License getLicenseByIdOrName(final String licenseIdOrName) {
return getLicenseQueryManager().getLicenseByIdOrName(licenseIdOrName);
}

License synchronizeLicense(License license, boolean commitIndex) {
return getLicenseQueryManager().synchronizeLicense(license, commitIndex);
}
Expand All @@ -691,6 +695,10 @@ public License createCustomLicense(License license, boolean commitIndex) {
return getLicenseQueryManager().createCustomLicense(license, commitIndex);
}

public License getCustomLicenseByName(final String licenseName) {
return getLicenseQueryManager().getCustomLicenseByName(licenseName);
}

public void deleteLicense(final License license, final boolean commitIndex) {
getLicenseQueryManager().deleteLicense(license, commitIndex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -730,8 +730,7 @@ private static void resolveAndApplyLicense(
// by priority, and simply take the first resolvable candidate.
for (final org.cyclonedx.model.License licenseCandidate : component.getLicenseCandidates()) {
if (isNotBlank(licenseCandidate.getId())) {
final License resolvedLicense = licenseCache.computeIfAbsent(licenseCandidate.getId(),
licenseId -> resolveLicense(qm, licenseId));
final License resolvedLicense = licenseCache.computeIfAbsent(licenseCandidate.getId(), qm::getLicenseByIdOrName);
if (resolvedLicense != License.UNRESOLVED) {
component.setResolvedLicense(resolvedLicense);
component.setLicenseUrl(trimToNull(licenseCandidate.getUrl()));
Expand All @@ -740,16 +739,15 @@ private static void resolveAndApplyLicense(
}

if (isNotBlank(licenseCandidate.getName())) {
final License resolvedLicense = licenseCache.computeIfAbsent(licenseCandidate.getName(),
licenseName -> resolveLicense(qm, licenseName));
final License resolvedLicense = licenseCache.computeIfAbsent(licenseCandidate.getName(), qm::getLicenseByIdOrName);
if (resolvedLicense != License.UNRESOLVED) {
component.setResolvedLicense(resolvedLicense);
component.setLicenseUrl(trimToNull(licenseCandidate.getUrl()));
break;
}

final License resolvedCustomLicense = customLicenseCache.computeIfAbsent(licenseCandidate.getName(),
licenseName -> resolveCustomLicense(qm, licenseName));
final License resolvedCustomLicense = customLicenseCache.computeIfAbsent(
licenseCandidate.getName(), qm::getCustomLicenseByName);
if (resolvedCustomLicense != License.UNRESOLVED) {
component.setResolvedLicense(resolvedCustomLicense);
component.setLicenseUrl(trimToNull(licenseCandidate.getUrl()));
Expand All @@ -771,30 +769,6 @@ private static void resolveAndApplyLicense(
}
}

private static License resolveLicense(final QueryManager qm, final String licenseIdOrName) {
final Query<License> query = qm.getPersistenceManager().newQuery(License.class);
query.setFilter("licenseId == :licenseIdOrName || name == :licenseIdOrName");
query.setNamedParameters(Map.of("licenseIdOrName", licenseIdOrName));
try {
final License license = query.executeUnique();
return license != null ? license : License.UNRESOLVED;
} finally {
query.closeAll();
}
}

private static License resolveCustomLicense(final QueryManager qm, final String licenseName) {
final Query<License> query = qm.getPersistenceManager().newQuery(License.class);
query.setFilter("name == :name && customLicense == true");
query.setParameters(licenseName);
try {
final License license = query.executeUnique();
return license != null ? license : License.UNRESOLVED;
} finally {
query.closeAll();
}
}

private static <T> Set<Long> getAllComponentIds(final QueryManager qm, final Project project, final Class<T> clazz) {
final Query<T> query = qm.getPersistenceManager().newQuery(clazz);
query.setFilter("project == :project");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,56 @@ public void informWithExistingComponentPropertiesAndBomWithComponentProperties()
});
}

@Test // https://github.com/DependencyTrack/dependency-track/issues/3957
public void informIssue3957Test() throws Exception {
final var licenseA = new License();
licenseA.setLicenseId("GPL-1.0");
licenseA.setName("GNU General Public License v1.0 only");
qm.persist(licenseA);

final var licenseB = new License();
licenseB.setLicenseId("GPL-1.0-only");
licenseB.setName("GNU General Public License v1.0 only");
qm.persist(licenseB);

final var project = new Project();
project.setName("acme-license-app");
qm.persist(project);

final byte[] bomBytes = """
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b80",
"version": 1,
"components": [
{
"type": "library",
"name": "acme-lib-x",
"licenses": [
{
"license": {
"name": "GNU General Public License v1.0 only"
}
}
]
}
]
}
""".getBytes(StandardCharsets.UTF_8);

final var bomUploadEvent = new BomUploadEvent(qm.detach(Project.class, project.getId()), createTempBomFile(bomBytes));
qm.createWorkflowSteps(bomUploadEvent.getChainIdentifier());
new BomUploadProcessingTask().inform(bomUploadEvent);
assertBomProcessedNotification();

qm.getPersistenceManager().evictAll();
assertThat(qm.getAllComponents(project)).satisfiesExactly(component -> {
assertThat(component.getResolvedLicense()).isNotNull();
assertThat(component.getResolvedLicense().getLicenseId()).isEqualTo("GPL-1.0");
});
}

private void assertBomProcessedNotification() throws Exception {
try {
assertThat(kafkaMockProducer.history()).anySatisfy(record -> {
Expand Down