From 74ec039a7ffdeb610d5686ceb639f8257a6e8ada Mon Sep 17 00:00:00 2001 From: George Chan Date: Mon, 26 Feb 2024 13:54:33 -0500 Subject: [PATCH 1/2] add Nuget support --- .../LicenseCheckRulesDefinition.java | 3 + .../licensecheck/LicenseCheckSensor.java | 7 +- .../licensecheck/ValidateLicenses.java | 2 + .../NugetLicenseDependencyScanner.java | 101 ++++++++++++++++++ src/main/web/dashboard/dependencies.vue | 2 +- .../LicenseCheckRulesDefinitionTest.java | 2 +- .../NugetLicenseDependencyScannerTest.java | 78 ++++++++++++++ src/test/resources/licenses.json | 63 +++++++++++ 8 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 src/main/java/at/porscheinformatik/sonarqube/licensecheck/nugetlicense/NugetLicenseDependencyScanner.java create mode 100644 src/test/java/at/porscheinformatik/sonarqube/licensecheck/nugetlicense/NugetLicenseDependencyScannerTest.java create mode 100644 src/test/resources/licenses.json diff --git a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinition.java b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinition.java index 9f92036d..4c8a6694 100644 --- a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinition.java +++ b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinition.java @@ -14,6 +14,7 @@ public final class LicenseCheckRulesDefinition implements RulesDefinition { public static final String LANG_GROOVY = "grvy"; public static final String LANG_KOTLIN = "kotlin"; public static final String LANG_SCALA = "scala"; + public static final String LANG_CS = "cs"; public static final String RULE_REPO_KEY = "licensecheck"; public static final String RULE_REPO_KEY_JS = "licensecheck-js"; @@ -21,6 +22,7 @@ public final class LicenseCheckRulesDefinition implements RulesDefinition { public static final String RULE_REPO_KEY_GROOVY = "licensecheck-groovy"; public static final String RULE_REPO_KEY_KOTLIN = "licensecheck-kotlin"; public static final String RULE_REPO_KEY_SCALA = "licensecheck-scala"; + public static final String RULE_REPO_KEY_CS = "licensecheck-cs"; public static final String RULE_UNLISTED_KEY = "licensecheck.unlisted"; public static final String RULE_NOT_ALLOWED_LICENSE_KEY = "licensecheck.notallowedlicense"; @@ -34,6 +36,7 @@ public void define(Context context) { context.createRepository(RULE_REPO_KEY_GROOVY, LANG_GROOVY), context.createRepository(RULE_REPO_KEY_KOTLIN, LANG_KOTLIN), context.createRepository(RULE_REPO_KEY_SCALA, LANG_SCALA), + context.createRepository(RULE_REPO_KEY_CS, LANG_CS) }; for (NewRepository repo : repos) { diff --git a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java index 6722fad4..13bfc1dd 100644 --- a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java +++ b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java @@ -5,6 +5,7 @@ import static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_JS; import static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_KOTLIN; import static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_TS; +import static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_CS; import static java.util.function.Predicate.not; import at.porscheinformatik.sonarqube.licensecheck.gradle.GradleDependencyScanner; @@ -22,6 +23,7 @@ import org.sonar.api.scanner.fs.InputProject; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; +import at.porscheinformatik.sonarqube.licensecheck.nugetlicense.NugetLicenseDependencyScanner; public class LicenseCheckSensor implements Sensor { @@ -49,6 +51,8 @@ public LicenseCheckSensor( ), new MavenDependencyScanner(licenseMappingService), new GradleDependencyScanner(licenseMappingService), + new GradleDependencyScanner(licenseMappingService), + new NugetLicenseDependencyScanner(licenseMappingService) }; } @@ -145,7 +149,8 @@ public void describe(SensorDescriptor descriptor) { RULE_REPO_KEY_JS, RULE_REPO_KEY_TS, RULE_REPO_KEY_GROOVY, - RULE_REPO_KEY_KOTLIN + RULE_REPO_KEY_KOTLIN, + RULE_REPO_KEY_CS ); } diff --git a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/ValidateLicenses.java b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/ValidateLicenses.java index 8ba46533..6f7c49b9 100644 --- a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/ValidateLicenses.java +++ b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/ValidateLicenses.java @@ -249,6 +249,8 @@ private static String getRepoKey(Dependency dependency) { return LicenseCheckRulesDefinition.RULE_REPO_KEY_TS; case LicenseCheckRulesDefinition.LANG_GROOVY: return LicenseCheckRulesDefinition.RULE_REPO_KEY_GROOVY; + case LicenseCheckRulesDefinition.LANG_CS: + return LicenseCheckRulesDefinition.RULE_REPO_KEY_CS; case LicenseCheckRulesDefinition.LANG_SCALA: return LicenseCheckRulesDefinition.RULE_REPO_KEY_SCALA; case LicenseCheckRulesDefinition.LANG_KOTLIN: diff --git a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/nugetlicense/NugetLicenseDependencyScanner.java b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/nugetlicense/NugetLicenseDependencyScanner.java new file mode 100644 index 00000000..726fc0a2 --- /dev/null +++ b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/nugetlicense/NugetLicenseDependencyScanner.java @@ -0,0 +1,101 @@ +package at.porscheinformatik.sonarqube.licensecheck.nugetlicense; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Set; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; + +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import at.porscheinformatik.sonarqube.licensecheck.Dependency; +import at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition; +import at.porscheinformatik.sonarqube.licensecheck.Scanner; +import at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService; + +public class NugetLicenseDependencyScanner implements Scanner +{ + private static final Logger LOGGER = Loggers.get(NugetLicenseDependencyScanner.class); + + private final LicenseMappingService licenseMappingService; + + public NugetLicenseDependencyScanner(LicenseMappingService licenseMappingService) + { + this.licenseMappingService = licenseMappingService; + } + + @Override + public Set scan(SensorContext context) + { + LOGGER.debug("Finding and scanning licenses.json"); + + FileSystem fs = context.fileSystem(); + FilePredicate licenseJsonPredicate = fs.predicates().matchesPathPattern("**/licenses.json"); + + Set allDependencies = new HashSet<>(); + + for (InputFile licenseJsonFile : fs.inputFiles(licenseJsonPredicate)) + { + context.markForPublishing(licenseJsonFile); + LOGGER.info("Scanning for licenses (file={})", licenseJsonFile.toString()); + allDependencies.addAll(dependencyParser(licenseJsonFile)); + } + + LOGGER.debug("Nuget scanning complete."); + + return allDependencies; + } + + private Set dependencyParser(InputFile licenseJsonFile) + { + Set dependencies = new HashSet<>(); + + try (InputStream fis = licenseJsonFile.inputStream(); + JsonReader jsonReader = Json.createReader(fis)) + { + JsonArray licensesJson = jsonReader.readArray(); + + if (licensesJson != null) + { + for (int i = 0; i < licensesJson.size(); i++) + { + JsonObject nextPackage = licensesJson.getJsonObject(i); + String packageName = nextPackage.getString("PackageName"); + String packageVersion = nextPackage.getString("PackageVersion"); + String packageLicense = nextPackage.getString("LicenseType"); + + if (dependencies.stream().anyMatch(d -> packageName.equals(d.getName()) && packageVersion.equals(d.getVersion()))) + { + LOGGER.debug("Package {} {} has already been encountered and will not be scanned again", packageName, packageVersion); + continue; + } + + String license = licenseMappingService.mapLicense(packageLicense); + + LOGGER.debug("Found license. Name: {} Version: {} License: {}", packageName, packageVersion, packageLicense); + + Dependency dependency = new Dependency(packageName, packageVersion, license, LicenseCheckRulesDefinition.LANG_CS); + dependency.setInputComponent(licenseJsonFile); + dependency.setTextRange(licenseJsonFile.selectLine(1)); + + dependencies.add(dependency); + } + } + } + catch (IOException e) + { + LOGGER.error("Error reading license.json", e); + } + + return dependencies; + } +} \ No newline at end of file diff --git a/src/main/web/dashboard/dependencies.vue b/src/main/web/dashboard/dependencies.vue index c39993bb..b01e081f 100644 --- a/src/main/web/dashboard/dependencies.vue +++ b/src/main/web/dashboard/dependencies.vue @@ -3,7 +3,7 @@

Dependencies

diff --git a/src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinitionTest.java b/src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinitionTest.java index 405f0c16..0b0eb408 100644 --- a/src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinitionTest.java +++ b/src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinitionTest.java @@ -14,7 +14,7 @@ public void define() { new LicenseCheckRulesDefinition().define(context); - assertThat(context.repositories().size(), is(6)); + assertThat(context.repositories().size(), is(7)); for (RulesDefinition.Repository repository : context.repositories()) { assertThat(repository.rules().size(), is(2)); } diff --git a/src/test/java/at/porscheinformatik/sonarqube/licensecheck/nugetlicense/NugetLicenseDependencyScannerTest.java b/src/test/java/at/porscheinformatik/sonarqube/licensecheck/nugetlicense/NugetLicenseDependencyScannerTest.java new file mode 100644 index 00000000..b7f52e55 --- /dev/null +++ b/src/test/java/at/porscheinformatik/sonarqube/licensecheck/nugetlicense/NugetLicenseDependencyScannerTest.java @@ -0,0 +1,78 @@ +package at.porscheinformatik.sonarqube.licensecheck.nugetlicense; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Set; + +import org.junit.Test; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.sensor.SensorContext; + +import at.porscheinformatik.sonarqube.licensecheck.Dependency; +import at.porscheinformatik.sonarqube.licensecheck.Scanner; +import at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService; + +public class NugetLicenseDependencyScannerTest +{ + private static final File RESOURCE_FOLDER = new File("src/test/resources"); + + private SensorContext createContext(File folder) + { + SensorContext context = mock(SensorContext.class); + InputFile packageJson = mock(InputFile.class); + when(packageJson.language()).thenReturn("json"); + when(packageJson.filename()).thenReturn("licenses.json"); + when(packageJson.relativePath()).thenReturn("/licenses.json"); + when(packageJson.type()).thenReturn(InputFile.Type.MAIN); + try + { + when(packageJson.inputStream()).thenAnswer(i -> new FileInputStream(new File(folder, "licenses.json"))); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + FileSystem fileSystem = new DefaultFileSystem(folder.toPath()).add(packageJson); + when(context.fileSystem()).thenReturn(fileSystem); + return context; + } + + @Test + public void testHappyPath() + { + Set dependencies = createScanner().scan(createContext(RESOURCE_FOLDER)); + + assertThat(dependencies, hasSize(4)); + assertThat(dependencies, containsInAnyOrder( + new Dependency("MonoGame.Content.Builder.Task", "3.8.0.1641", "MS-PL"), + new Dependency("MonoGame.Framework.DesktopGL", "3.8.0.1641", "MS-PL"), + new Dependency("CommandLineParser", "2.8.0", "License.md"), + new Dependency("Newtonsoft.Json", "13.0.1", "MIT"))); + } + + @Test + public void testNoPackageJson() + { + Set dependencies = createScanner().scan(createContext(new File("src"))); + + assertThat(dependencies, hasSize(0)); + } + + private Scanner createScanner() + { + LicenseMappingService licenseMappingService = mock(LicenseMappingService.class); + when(licenseMappingService.mapLicense(anyString())).thenCallRealMethod(); + + return new NugetLicenseDependencyScanner(licenseMappingService); + } +} \ No newline at end of file diff --git a/src/test/resources/licenses.json b/src/test/resources/licenses.json new file mode 100644 index 00000000..b205e6f3 --- /dev/null +++ b/src/test/resources/licenses.json @@ -0,0 +1,63 @@ +[ + { + "PackageName": "MonoGame.Content.Builder.Task", + "PackageVersion": "3.8.0.1641", + "PackageUrl": "https://www.monogame.net/", + "Copyright": "", + "Authors": [ + "MonoGame Team" + ], + "Description": "MSBuild task to automatically build content for MonoGame.", + "LicenseUrl": "https://licenses.nuget.org/MS-PL", + "LicenseType": "MS-PL", + "Repository": { + "Type": "", + "Url": "https://github.com/MonoGame/MonoGame", + "Commit": "" + } + }, + { + "PackageName": "MonoGame.Framework.DesktopGL", + "PackageVersion": "3.8.0.1641", + "PackageUrl": "https://www.monogame.net/", + "Copyright": "", + "Authors": [ + "MonoGame Team" + ], + "Description": "The MonoGame runtime supporting Windows, Linux and macOS using SDL2 and OpenGL.", + "LicenseUrl": "https://licenses.nuget.org/MS-PL", + "LicenseType": "MS-PL", + "Repository": { + "Type": "", + "Url": "https://github.com/MonoGame/MonoGame", + "Commit": "" + } + }, + { + "PackageName": "CommandLineParser", + "PackageVersion": "2.8.0", + "PackageUrl": "https://github.com/commandlineparser/commandline", + "Copyright": "Copyright (c) 2005 - 2020 Giacomo Stelluti Scala & Contributors", + "Authors": [ + "gsscoder", + "nemec", + "ericnewton76", + "moh-hassan" + ], + "Description": "Terse syntax C# command line parser for .NET. For FSharp support see CommandLineParser.FSharp. The Command Line Parser Library offers to CLR applications a clean and concise API for manipulating command line arguments and related tasks.", + "LicenseUrl": "https://www.nuget.org/packages/CommandLineParser/2.8.0/License", + "LicenseType": "License.md" + }, + { + "PackageName": "Newtonsoft.Json", + "PackageVersion": "13.0.1", + "PackageUrl": "https://www.newtonsoft.com/json", + "Copyright": "Copyright © James Newton-King 2008", + "Authors": [ + "James Newton-King" + ], + "Description": "Json.NET is a popular high-performance JSON framework for .NET", + "LicenseUrl": "https://licenses.nuget.org/MIT", + "LicenseType": "MIT" + } +] \ No newline at end of file From a72de59d4edca933a39e734672aab6f7adb6173d Mon Sep 17 00:00:00 2001 From: cloud122 Date: Tue, 30 Apr 2024 19:56:51 -0400 Subject: [PATCH 2/2] Update LicenseCheckSensor.java Removed duplicate entry. --- .../sonarqube/licensecheck/LicenseCheckSensor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java index 13bfc1dd..b56843f1 100644 --- a/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java +++ b/src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java @@ -51,7 +51,6 @@ public LicenseCheckSensor( ), new MavenDependencyScanner(licenseMappingService), new GradleDependencyScanner(licenseMappingService), - new GradleDependencyScanner(licenseMappingService), new NugetLicenseDependencyScanner(licenseMappingService) }; }
- Here you see all project dependencies from Maven (including transitive) and NPM. + Here you see all project dependencies from Maven (including transitive), NPM and NuGet.