diff --git a/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptProfilesDefinition.java b/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptProfilesDefinition.java index 1a660ee6608..c1ed21be1db 100644 --- a/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptProfilesDefinition.java +++ b/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptProfilesDefinition.java @@ -19,12 +19,19 @@ */ package org.sonar.plugins.javascript; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.sonar.api.internal.google.common.annotations.VisibleForTesting; +import org.sonar.api.rule.RuleKey; import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; import org.sonar.check.Rule; import org.sonar.javascript.checks.CheckList; import org.sonar.plugins.javascript.api.JavaScriptCheck; @@ -32,6 +39,8 @@ public class JavaScriptProfilesDefinition implements BuiltInQualityProfilesDefinition { + private static final Logger LOG = Loggers.get(JavaScriptProfilesDefinition.class); + static final String SONAR_WAY = "Sonar way"; // unfortunately we have this inconsistency in names // in order to keep compatibility we should stick to these names @@ -43,6 +52,9 @@ public class JavaScriptProfilesDefinition implements BuiltInQualityProfilesDefin public static final String SONAR_WAY_RECOMMENDED_JSON = RESOURCE_PATH + "/Sonar_way_recommended_profile.json"; private static final Map PROFILES = new HashMap<>(); + static final String SECURITY_RULES_CLASS_NAME = "com.sonar.plugins.security.api.JsRules"; + public static final String SECURITY_RULE_KEYS_METHOD_NAME = "getSecurityRuleKeys"; + static { PROFILES.put(SONAR_WAY, SONAR_WAY_JSON); PROFILES.put(SONAR_WAY_RECOMMENDED_JS, SONAR_WAY_RECOMMENDED_JSON); @@ -80,12 +92,48 @@ private static void createProfile(String profileName, String language, Set ruleKeys = getSecurityRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, language); + LOG.warn("Adding security ruleKeys {}", ruleKeys); + ruleKeys.forEach(r -> newProfile.activateRule(r.repository(), r.rule())); + } + + @VisibleForTesting + static Set getSecurityRuleKeys(String className, String ruleKeysMethodName, String language) { + try { + Class rulesClass = Class.forName(className); + Method getRuleKeysMethod = rulesClass.getMethod(ruleKeysMethodName, String.class); + return (Set) getRuleKeysMethod.invoke(null, language); + } catch (ClassNotFoundException e) { + LOG.debug(className + " is not found, " + securityRuleMessage(e)); + } catch (NoSuchMethodException e) { + LOG.debug("Method not found on " + className +", " + securityRuleMessage(e)); + } catch (IllegalAccessException | InvocationTargetException e) { + LOG.debug(e.getClass().getSimpleName() + ": " + securityRuleMessage(e)); + } + + return Collections.emptySet(); + } + private static Set ruleKeys(List> checks) { return checks.stream() .map(c -> c.getAnnotation(Rule.class).key()) .collect(Collectors.toSet()); } + + private static String securityRuleMessage(Exception e) { + return "no security rules added to builtin profile: " + e.getMessage(); + } } diff --git a/sonar-javascript-plugin/src/test/java/com/sonar/plugins/security/api/JsRules.java b/sonar-javascript-plugin/src/test/java/com/sonar/plugins/security/api/JsRules.java new file mode 100644 index 00000000000..5cc5e95a0f9 --- /dev/null +++ b/sonar-javascript-plugin/src/test/java/com/sonar/plugins/security/api/JsRules.java @@ -0,0 +1,47 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2020 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 com.sonar.plugins.security.api; + +import java.util.HashSet; +import java.util.Set; +import org.sonar.api.rule.RuleKey; + +public class JsRules { + + public static final Set JS_RULES = new HashSet<>(); + public static final Set TS_RULES = new HashSet<>(); + + public static Set getSecurityRuleKeys(String language) { + switch (language) { + case "js": + return JS_RULES; + case "ts": + return TS_RULES; + default: + throw new IllegalArgumentException("wrong language"); + } + } + + public static void clear() { + JS_RULES.clear(); + TS_RULES.clear(); + } + +} diff --git a/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptProfilesDefinitionTest.java b/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptProfilesDefinitionTest.java index 7b4d6969d5f..a74ec424154 100644 --- a/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptProfilesDefinitionTest.java +++ b/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptProfilesDefinitionTest.java @@ -19,12 +19,14 @@ */ package org.sonar.plugins.javascript; +import com.sonar.plugins.security.api.JsRules; import java.lang.annotation.Annotation; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.junit.Before; import org.junit.Test; +import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile; @@ -36,8 +38,11 @@ import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.plugins.javascript.JavaScriptProfilesDefinition.SECURITY_RULES_CLASS_NAME; +import static org.sonar.plugins.javascript.JavaScriptProfilesDefinition.SECURITY_RULE_KEYS_METHOD_NAME; import static org.sonar.plugins.javascript.JavaScriptProfilesDefinition.SONAR_WAY_JSON; import static org.sonar.plugins.javascript.JavaScriptProfilesDefinition.SONAR_WAY_RECOMMENDED_JSON; +import static org.sonar.plugins.javascript.JavaScriptProfilesDefinition.getSecurityRuleKeys; public class JavaScriptProfilesDefinitionTest { private final BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); @@ -128,4 +133,31 @@ public void no_legacy_Key_in_profile_json() { assertThat(sonarWayKeys).isSubsetOf(allKeys); assertThat(sonarRecommendedWayKeys).isSubsetOf(allKeys); } + + @Test + public void should_contains_security_rules_if_available() { + // no security rule available + assertThat(getSecurityRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "js")) + .isEmpty(); + + assertThat(getSecurityRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "ts")) + .isEmpty(); + + JsRules.JS_RULES.add(RuleKey.parse("jssecurity:S3649")); + // one security rule available + assertThat(getSecurityRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "js")) + .containsOnly(RuleKey.of("jssecurity", "S3649")); + + JsRules.TS_RULES.add(RuleKey.parse("tssecurity:S3649")); + assertThat(getSecurityRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "ts")) + .containsOnly(RuleKey.of("tssecurity", "S3649")); + + // invalid class name + assertThat(getSecurityRuleKeys("xxx", SECURITY_RULE_KEYS_METHOD_NAME, "js")).isEmpty(); + + // invalid method name + assertThat(getSecurityRuleKeys(SECURITY_RULES_CLASS_NAME, "xxx", "js")).isEmpty(); + + JsRules.clear(); + } }