entry 'scribejava-apis' | entry 'scribejava-apis' | ||||
entry 'scribejava-core' | entry 'scribejava-core' | ||||
} | } | ||||
dependency 'com.github.everit-org.json-schema:org.everit.json.schema:1.12.2' | |||||
// This project is no longer maintained and was forked | // This project is no longer maintained and was forked | ||||
// by https://github.com/java-diff-utils/java-diff-utils | // by https://github.com/java-diff-utils/java-diff-utils | ||||
// (io.github.java-diff-utils:java-diff-utils). | // (io.github.java-diff-utils:java-diff-utils). |
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import javax.annotation.CheckForNull; | import javax.annotation.CheckForNull; | ||||
import org.sonar.api.Plugin; | import org.sonar.api.Plugin; | ||||
import org.sonar.core.platform.PluginInfo; | import org.sonar.core.platform.PluginInfo; | ||||
import org.sonar.core.platform.PluginRepository; | import org.sonar.core.platform.PluginRepository; |
// please keep the list grouped by configuration and ordered by name | // please keep the list grouped by configuration and ordered by name | ||||
compile 'com.google.guava:guava' | compile 'com.google.guava:guava' | ||||
compile 'com.github.everit-org.json-schema:org.everit.json.schema' | |||||
compile project(':server:sonar-ce-common') | compile project(':server:sonar-ce-common') | ||||
compile project(':server:sonar-ce-task') | compile project(':server:sonar-ce-task') | ||||
compile project(':server:sonar-db-dao') | compile project(':server:sonar-db-dao') |
import com.google.common.collect.ImmutableSet; | import com.google.common.collect.ImmutableSet; | ||||
import com.google.gson.Gson; | import com.google.gson.Gson; | ||||
import com.google.gson.JsonElement; | import com.google.gson.JsonElement; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | |||||
import java.util.Collection; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Locale; | import java.util.Locale; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import javax.annotation.CheckForNull; | import javax.annotation.CheckForNull; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.everit.json.schema.ValidationException; | |||||
import org.everit.json.schema.loader.SchemaLoader; | |||||
import org.json.JSONObject; | |||||
import org.json.JSONTokener; | |||||
import org.sonar.api.PropertyType; | import org.sonar.api.PropertyType; | ||||
import org.sonar.api.config.PropertyDefinition; | import org.sonar.api.config.PropertyDefinition; | ||||
import org.sonar.api.config.PropertyDefinitions; | import org.sonar.api.config.PropertyDefinitions; | ||||
import org.sonar.server.exceptions.BadRequestException; | import org.sonar.server.exceptions.BadRequestException; | ||||
import static java.lang.String.format; | import static java.lang.String.format; | ||||
import static java.util.Arrays.asList; | |||||
import static java.util.Objects.requireNonNull; | import static java.util.Objects.requireNonNull; | ||||
import static org.sonar.server.exceptions.BadRequestException.checkRequest; | import static org.sonar.server.exceptions.BadRequestException.checkRequest; | ||||
public class SettingValidations { | public class SettingValidations { | ||||
private static final Collection<String> SECURITY_JSON_PROPERTIES = asList( | |||||
"sonar.security.config.javasecurity", | |||||
"sonar.security.config.phpsecurity", | |||||
"sonar.security.config.pythonsecurity", | |||||
"sonar.security.config.roslyn.sonaranalyzer.security.cs" | |||||
); | |||||
private final PropertyDefinitions definitions; | private final PropertyDefinitions definitions; | ||||
private final DbClient dbClient; | private final DbClient dbClient; | ||||
private final I18n i18n; | private final I18n i18n; | ||||
} | } | ||||
private class ValueTypeValidation implements Consumer<SettingData> { | private class ValueTypeValidation implements Consumer<SettingData> { | ||||
@Override | @Override | ||||
public void accept(SettingData data) { | public void accept(SettingData data) { | ||||
PropertyDefinition definition = definitions.get(data.key); | PropertyDefinition definition = definitions.get(data.key); | ||||
} else if (definition.type() == PropertyType.USER_LOGIN) { | } else if (definition.type() == PropertyType.USER_LOGIN) { | ||||
validateLogin(data); | validateLogin(data); | ||||
} else if (definition.type() == PropertyType.JSON) { | } else if (definition.type() == PropertyType.JSON) { | ||||
validateJson(data); | |||||
validateJson(data, definition); | |||||
} else { | } else { | ||||
validateOtherTypes(data, definition); | validateOtherTypes(data, definition); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private void validateJson(SettingData data) { | |||||
private void validateJson(SettingData data, PropertyDefinition definition) { | |||||
Optional<String> jsonContent = data.values.stream().findFirst(); | Optional<String> jsonContent = data.values.stream().findFirst(); | ||||
if (jsonContent.isPresent()) { | if (jsonContent.isPresent()) { | ||||
try { | try { | ||||
new Gson().getAdapter(JsonElement.class).fromJson(jsonContent.get()); | new Gson().getAdapter(JsonElement.class).fromJson(jsonContent.get()); | ||||
} catch (IOException e) { | |||||
validateJsonSchema(jsonContent.get(), definition); | |||||
} catch (ValidationException e) { | |||||
throw new IllegalArgumentException(String.format("Provided JSON is invalid [%s]", e.getMessage())); | |||||
} catch (IOException e){ | |||||
throw new IllegalArgumentException("Provided JSON is invalid"); | throw new IllegalArgumentException("Provided JSON is invalid"); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private void validateJsonSchema(String json, PropertyDefinition definition) { | |||||
if(SECURITY_JSON_PROPERTIES.contains(definition.key())){ | |||||
InputStream jsonSchemaInputStream = this.getClass().getClassLoader().getResourceAsStream("json-schemas/security.json"); | |||||
if(jsonSchemaInputStream != null){ | |||||
JSONObject jsonSchema = new JSONObject(new JSONTokener(jsonSchemaInputStream)); | |||||
JSONObject jsonSubject = new JSONObject(new JSONTokener(json)); | |||||
SchemaLoader.load(jsonSchema).validate(jsonSubject); | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
{ | |||||
"$schema": "http://json-schema.org/draft-07/schema#", | |||||
"title": "Custom configuration schema", | |||||
"description": "Schema to validate custom configuration given as input to the custom configuration properties", | |||||
"definitions": { | |||||
"Interval": { | |||||
"type": "object", | |||||
"properties": { | |||||
"fromIndex": { | |||||
"type": "integer" | |||||
} | |||||
}, | |||||
"additionalProperties": false | |||||
}, | |||||
"CommonConfiguration": { | |||||
"type": "object", | |||||
"properties": { | |||||
"args": { | |||||
"type": "array", | |||||
"items": { | |||||
"type": "integer" | |||||
} | |||||
}, | |||||
"interval": { | |||||
"$ref": "#/definitions/Interval" | |||||
}, | |||||
"isMethodPrefix": { | |||||
"type": "boolean" | |||||
}, | |||||
"isShallow": { | |||||
"type": "boolean" | |||||
}, | |||||
"isWhitelist": { | |||||
"type": "boolean" | |||||
}, | |||||
"methodId": { | |||||
"type": "string" | |||||
} | |||||
}, | |||||
"required": [ | |||||
"methodId" | |||||
], | |||||
"additionalProperties": false | |||||
}, | |||||
"RuleConfiguration": { | |||||
"type": "object", | |||||
"properties": { | |||||
"decoders": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/definitions/CommonConfiguration" | |||||
} | |||||
}, | |||||
"encoders": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/definitions/CommonConfiguration" | |||||
} | |||||
}, | |||||
"passthroughs": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/definitions/CommonConfiguration" | |||||
} | |||||
}, | |||||
"sanitizers": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/definitions/CommonConfiguration" | |||||
} | |||||
}, | |||||
"sinks": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/definitions/CommonConfiguration" | |||||
} | |||||
}, | |||||
"sources": { | |||||
"type": "array", | |||||
"items": { | |||||
"$ref": "#/definitions/CommonConfiguration" | |||||
} | |||||
} | |||||
}, | |||||
"additionalProperties": false | |||||
} | |||||
}, | |||||
"type": "object", | |||||
"additionalProperties": { | |||||
"$ref": "#/definitions/RuleConfiguration" | |||||
} | |||||
} |
import java.util.Random; | import java.util.Random; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; |
import com.google.common.collect.ImmutableMap; | import com.google.common.collect.ImmutableMap; | ||||
import com.google.gson.Gson; | import com.google.gson.Gson; | ||||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||||
import java.net.HttpURLConnection; | import java.net.HttpURLConnection; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Random; | import java.util.Random; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.junit.rules.ExpectedException; | import org.junit.rules.ExpectedException; | ||||
import org.junit.runner.RunWith; | |||||
import org.sonar.api.PropertyType; | import org.sonar.api.PropertyType; | ||||
import org.sonar.api.config.PropertyDefinition; | import org.sonar.api.config.PropertyDefinition; | ||||
import org.sonar.api.config.PropertyDefinitions; | import org.sonar.api.config.PropertyDefinitions; | ||||
import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto; | import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto; | ||||
import static org.sonar.db.user.UserTesting.newUserDto; | import static org.sonar.db.user.UserTesting.newUserDto; | ||||
@RunWith(DataProviderRunner.class) | |||||
public class SetActionTest { | public class SetActionTest { | ||||
private static final Gson GSON = GsonHelper.create(); | private static final Gson GSON = GsonHelper.create(); | ||||
userSession.logIn().setSystemAdministrator(); | userSession.logIn().setSystemAdministrator(); | ||||
} | } | ||||
@DataProvider | |||||
public static Object[][] securityJsonProperties() { | |||||
return new Object[][] { | |||||
{"sonar.security.config.javasecurity"}, | |||||
{"sonar.security.config.phpsecurity"}, | |||||
{"sonar.security.config.pythonsecurity"}, | |||||
{"sonar.security.config.roslyn.sonaranalyzer.security.cs"} | |||||
}; | |||||
} | |||||
@Test | @Test | ||||
public void empty_204_response() { | public void empty_204_response() { | ||||
TestResponse result = ws.newRequest() | TestResponse result = ws.newRequest() | ||||
.hasMessage("Provided JSON is invalid"); | .hasMessage("Provided JSON is invalid"); | ||||
} | } | ||||
@Test | |||||
@UseDataProvider("securityJsonProperties") | |||||
public void successfully_validate_json_schema(String securityPropertyKey) { | |||||
String security_custom_config = "{\n" + | |||||
" \"S3649\": {\n" + | |||||
" \"sources\": [\n" + | |||||
" {\n" + | |||||
" \"methodId\": \"My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery\"\n" + | |||||
" }\n" + | |||||
" ],\n" + | |||||
" \"sanitizers\": [\n" + | |||||
" {\n" + | |||||
" \"methodId\": \"str_replace\"\n" + | |||||
" }\n" + | |||||
" ],\n" + | |||||
" \"sinks\": [\n" + | |||||
" {\n" + | |||||
" \"methodId\": \"mysql_query\",\n" + | |||||
" \"args\": [1]\n" + | |||||
" }\n" + | |||||
" ]\n" + | |||||
" }\n" + | |||||
"}"; | |||||
definitions.addComponent(PropertyDefinition | |||||
.builder(securityPropertyKey) | |||||
.name("foo") | |||||
.description("desc") | |||||
.category("cat") | |||||
.subCategory("subCat") | |||||
.type(PropertyType.JSON) | |||||
.build()); | |||||
callForGlobalSetting(securityPropertyKey, security_custom_config); | |||||
assertGlobalSetting(securityPropertyKey, security_custom_config); | |||||
} | |||||
@Test | |||||
@UseDataProvider("securityJsonProperties") | |||||
public void fail_json_schema_validation_when_property_has_incorrect_type(String securityPropertyKey) { | |||||
String security_custom_config = "{\n" + | |||||
" \"S3649\": {\n" + | |||||
" \"sources\": [\n" + | |||||
" {\n" + | |||||
" \"methodId\": \"My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery\"\n" + | |||||
" }\n" + | |||||
" ],\n" + | |||||
" \"sinks\": [\n" + | |||||
" {\n" + | |||||
" \"methodId\": 12345,\n" + | |||||
" \"args\": [1]\n" + | |||||
" }\n" + | |||||
" ]\n" + | |||||
" }\n" + | |||||
"}"; | |||||
definitions.addComponent(PropertyDefinition | |||||
.builder(securityPropertyKey) | |||||
.name("foo") | |||||
.description("desc") | |||||
.category("cat") | |||||
.subCategory("subCat") | |||||
.type(PropertyType.JSON) | |||||
.build()); | |||||
assertThatThrownBy(() -> callForGlobalSetting(securityPropertyKey, security_custom_config)) | |||||
.isInstanceOf(IllegalArgumentException.class) | |||||
.hasMessageContaining("S3649/sinks/0/methodId: expected type: String, found: Integer"); | |||||
} | |||||
@Test | |||||
@UseDataProvider("securityJsonProperties") | |||||
public void fail_json_schema_validation_when_property_has_unknown_attribute(String securityPropertyKey) { | |||||
String security_custom_config = "{\n" + | |||||
" \"S3649\": {\n" + | |||||
" \"sources\": [\n" + | |||||
" {\n" + | |||||
" \"methodId\": \"My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery\"\n" + | |||||
" }\n" + | |||||
" ],\n" + | |||||
" \"unknown\": [\n" + | |||||
" {\n" + | |||||
" \"methodId\": 12345,\n" + | |||||
" \"args\": [1]\n" + | |||||
" }\n" + | |||||
" ]\n" + | |||||
" }\n" + | |||||
"}"; | |||||
definitions.addComponent(PropertyDefinition | |||||
.builder(securityPropertyKey) | |||||
.name("foo") | |||||
.description("desc") | |||||
.category("cat") | |||||
.subCategory("subCat") | |||||
.type(PropertyType.JSON) | |||||
.build()); | |||||
assertThatThrownBy(() -> callForGlobalSetting(securityPropertyKey, security_custom_config)) | |||||
.isInstanceOf(IllegalArgumentException.class) | |||||
.hasMessageContaining("extraneous key [unknown] is not permitted"); | |||||
} | |||||
@Test | @Test | ||||
public void persist_global_setting_with_non_ascii_characters() { | public void persist_global_setting_with_non_ascii_characters() { | ||||
callForGlobalSetting("my.key", "fi±∞…"); | callForGlobalSetting("my.key", "fi±∞…"); |
} | } | ||||
// Check the size of the archive | // Check the size of the archive | ||||
zip.doLast { | zip.doLast { | ||||
def minLength = 237000000 | |||||
def maxLength = 257000000 | |||||
def minLength = 238000000 | |||||
def maxLength = 258000000 | |||||
def length = archiveFile.get().asFile.length() | def length = archiveFile.get().asFile.length() | ||||
if (length < minLength) | if (length < minLength) |