Skip to content

Commit

Permalink
Add security rules to built-in profiles (#2173)
Browse files Browse the repository at this point in the history
  • Loading branch information
saberduck authored Sep 25, 2020
1 parent 50ac749 commit f9298a3
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,28 @@
*/
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;
import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader;

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
Expand All @@ -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<String, String> 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);
Expand Down Expand Up @@ -80,12 +92,48 @@ private static void createProfile(String profileName, String language, Set<Strin
newProfile.activateRule("common-" + language, "DuplicatedBlocks");
}

addSecurityRules(newProfile, language);

newProfile.done();
}

/**
* Security rules are added by reflectively invoking specific class from sonar-security-plugin, which provides
* rule keys to add to the built-in profiles.
*
* It is expected for reflective call to fail in case sonar-security-plugin is not available, e.g. in SQ community
* edition
*/
private static void addSecurityRules(NewBuiltInQualityProfile newProfile, String language) {
Set<RuleKey> 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<RuleKey> getSecurityRuleKeys(String className, String ruleKeysMethodName, String language) {
try {
Class<?> rulesClass = Class.forName(className);
Method getRuleKeysMethod = rulesClass.getMethod(ruleKeysMethodName, String.class);
return (Set<RuleKey>) 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<String> ruleKeys(List<Class<? extends JavaScriptCheck>> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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<RuleKey> JS_RULES = new HashSet<>();
public static final Set<RuleKey> TS_RULES = new HashSet<>();

public static Set<RuleKey> 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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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();
}
}

0 comments on commit f9298a3

Please sign in to comment.