*/
package org.sonar.core.sarif;
-import com.google.common.annotations.VisibleForTesting;
import com.google.gson.annotations.SerializedName;
import java.util.Set;
public class Sarif210 {
- @VisibleForTesting
public static final String SARIF_VERSION = "2.1.0";
@SerializedName("version")
@Override
public Sarif210 deserialize(Path reportPath) {
try (Reader reader = newBufferedReader(reportPath, UTF_8)) {
- return gson.fromJson(reader, Sarif210.class);
+ Sarif210 sarif = gson.fromJson(reader, Sarif210.class);
+ SarifVersionValidator.validateSarifVersion(sarif.getVersion());
+ return sarif;
} catch (JsonIOException | IOException e) {
throw new IllegalStateException(format(SARIF_REPORT_ERROR, reportPath), e);
} catch (JsonSyntaxException e) {
throw new IllegalStateException(format(SARIF_JSON_SYNTAX_ERROR, reportPath), e);
+ } catch (IllegalStateException e) {
+ throw new IllegalStateException(e.getMessage(), e);
}
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.sarif;
+
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+import static java.lang.String.format;
+import static org.sonar.core.sarif.Sarif210.SARIF_VERSION;
+
+public class SarifVersionValidator {
+ public static final Set<String> SUPPORTED_SARIF_VERSIONS = Set.of(SARIF_VERSION);
+ public static final String UNSUPPORTED_VERSION_MESSAGE_TEMPLATE = "Version [%s] of SARIF is not supported";
+
+ private SarifVersionValidator() {}
+
+ public static void validateSarifVersion(@Nullable String version) {
+ if (!isSupportedSarifVersion(version)) {
+ throw new IllegalStateException(composeUnsupportedVersionMessage(version));
+ }
+ }
+
+ private static boolean isSupportedSarifVersion(@Nullable String version) {
+ return Optional.ofNullable(version)
+ .filter(SUPPORTED_SARIF_VERSIONS::contains)
+ .isPresent();
+ }
+
+ private static String composeUnsupportedVersionMessage(@Nullable String version) {
+ return format(UNSUPPORTED_VERSION_MESSAGE_TEMPLATE, version);
+ }
+}
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
+import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
+import static org.sonar.core.sarif.SarifVersionValidator.UNSUPPORTED_VERSION_MESSAGE_TEMPLATE;
@RunWith(MockitoJUnitRunner.class)
public class SarifSerializerTest {
assertThatThrownBy(() -> serializer.deserialize(sarif))
.isInstanceOf(IllegalStateException.class)
- .hasMessage(String.format("Failed to read SARIF report at '%s'", file));
+ .hasMessage(format("Failed to read SARIF report at '%s'", file));
}
@Test
assertThatThrownBy(() -> serializer.deserialize(sarif))
.isInstanceOf(IllegalStateException.class)
- .hasMessage(String.format("Failed to read SARIF report at '%s': invalid JSON syntax", sarif));
+ .hasMessage(format("Failed to read SARIF report at '%s': invalid JSON syntax", sarif));
+ }
+
+ @Test
+ public void deserialize_shouldFail_whenSarifVersionIsNotSupported() throws URISyntaxException {
+ URL sarifResource = requireNonNull(getClass().getResource("unsupported-sarif-version-abc.json"));
+ Path sarif = Paths.get(sarifResource.toURI());
+
+ assertThatThrownBy(() -> serializer.deserialize(sarif))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage(format(UNSUPPORTED_VERSION_MESSAGE_TEMPLATE, "A.B.C"));
}
private void verifySarif(Sarif210 deserializationResult) {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.sarif;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.sonar.core.sarif.SarifVersionValidator.SUPPORTED_SARIF_VERSIONS;
+import static org.sonar.core.sarif.SarifVersionValidator.UNSUPPORTED_VERSION_MESSAGE_TEMPLATE;
+
+@RunWith(DataProviderRunner.class)
+public class SarifVersionValidatorTest {
+
+ @Test
+ @UseDataProvider("unsupportedSarifVersions")
+ public void sarif_version_validation_fails_if_version_is_not_supported(String version) {
+ assertThatThrownBy(() -> SarifVersionValidator.validateSarifVersion(version))
+ .isExactlyInstanceOf(IllegalStateException.class)
+ .hasMessage(format(UNSUPPORTED_VERSION_MESSAGE_TEMPLATE, version));
+ }
+
+ @Test
+ @UseDataProvider("supportedSarifVersions")
+ public void sarif_version_validation_succeeds_if_version_is_supported(String version) {
+ assertThatCode(() -> SarifVersionValidator.validateSarifVersion(version))
+ .doesNotThrowAnyException();
+ }
+
+ @DataProvider
+ public static List<String> unsupportedSarifVersions() {
+ List<String> unsupportedVersions = generateRandomUnsupportedSemanticVersions(10);
+ unsupportedVersions.add(null);
+ return unsupportedVersions;
+ }
+
+ @DataProvider
+ public static Set<String> supportedSarifVersions() {
+ return SUPPORTED_SARIF_VERSIONS;
+ }
+
+ private static List<String> generateRandomUnsupportedSemanticVersions(int amount) {
+ return Stream
+ .generate(SarifVersionValidatorTest::generateRandomSemanticVersion)
+ .takeWhile(SarifVersionValidatorTest::isUnsupportedVersion)
+ .limit(amount)
+ .collect(Collectors.toList());
+ }
+
+ private static String generateRandomSemanticVersion() {
+ return IntStream
+ .rangeClosed(1, 3)
+ .mapToObj(x -> RandomStringUtils.randomNumeric(1))
+ .collect(Collectors.joining("."));
+ }
+
+ private static boolean isUnsupportedVersion(String version) {
+ return !SUPPORTED_SARIF_VERSIONS.contains(version);
+ }
+
+}
--- /dev/null
+{
+ "version": "A.B.C",
+ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+ "runs": [
+ {
+ "tool": {
+ "driver": {
+ "name": "SonarQube",
+ "organization": "SonarSource",
+ "semanticVersion": "9.6",
+ "rules": [
+ {
+ "id": "java:S5132",
+ "name": "java:S5132",
+ "shortDescription": {
+ "text": "Make this final static field too."
+ },
+ "fullDescription": {
+ "text": "Make this final static field too."
+ },
+ "help": {
+ "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam hendrerit nisi sed sollicitudin pellentesque. Nunc posuere purus rhoncus pulvinar aliquam. Ut aliquet tristique nisl vitae volutpat. Nulla aliquet porttitor venenatis. Donec a dui et dui fringilla consectetur id nec massa. Aliquam erat volutpat. Sed ut dui ut lacus dictum fermentum vel tincidunt neque. Sed sed lacinia lectus. Duis sit amet sodales felis. Duis nunc eros, mattis at dui ac, convallis semper risus. In adipiscing ultrices tellus, in suscipit massa vehicula eu."
+ },
+ "properties": {
+ "tags": [
+ "tag1",
+ "tag2"
+ ]
+ }
+ }
+ ]
+ }
+ },
+ "results": [
+ {
+ "ruleId": "java:S5132",
+ "message": {
+ "text": "this is the message"
+ },
+ "locations": [
+ {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "www.google.com",
+ "uriBaseId": "%SRCROOT"
+ },
+ "region": {
+ "startLine": 11,
+ "endLine": 222,
+ "startColumn": 54,
+ "endColumn": 4
+ }
+ }
+ }
+ ],
+ "partialFingerprints": {
+ "primaryLocationLineHash": "thisISTHEHAS"
+ },
+ "codeFlows": [
+ {
+ "threadFlows": [
+ {
+ "locations": [
+ {
+ "location": {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "www.google.com",
+ "uriBaseId": "%SRCROOT"
+ },
+ "region": {
+ "startLine": 11,
+ "endLine": 222,
+ "startColumn": 54,
+ "endColumn": 4
+ }
+ }
+ }
+ },
+ {
+ "location": {
+ "physicalLocation": {
+ "artifactLocation": {
+ "uri": "www.google.com",
+ "uriBaseId": "%SRCROOT"
+ },
+ "region": {
+ "startLine": 22,
+ "endLine": 4323,
+ "startColumn": 545,
+ "endColumn": 4324
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "language": "en-us",
+ "columnKind": "utf16CodeUnits"
+ }
+ ]
+}
import org.sonar.core.sarif.Sarif210;
import org.sonar.core.sarif.SarifSerializer;
-import static org.mockito.Mockito.doThrow;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Rule
public LogTester logTester = new LogTester();
- private SensorContextTester sensorContext = SensorContextTester.create(Path.of("."));
+ private final SensorContextTester sensorContext = SensorContextTester.create(Path.of("."));
@Test
public void execute_single_files() {