/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.apex.rule.security;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sourceforge.pmd.lang.apex.ast.ASTBinaryExpression;
import net.sourceforge.pmd.lang.apex.ast.ASTField;
import net.sourceforge.pmd.lang.apex.ast.ASTLiteralExpression;
import net.sourceforge.pmd.lang.apex.ast.ASTMethodCallExpression;
import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration;
import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression;
import net.sourceforge.pmd.lang.apex.ast.AbstractApexNode;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
/**
* Flags usage of http request.setHeader('Authorization',..) and suggests using
* named credentials which helps store credentials for the callout in a safe
* place.
*
* @author sergey.gorbaty
*
*/
public class ApexSuggestUsingNamedCredRule extends AbstractApexRule {
private static final String SET_HEADER = "setHeader";
private static final String AUTHORIZATION = "Authorization";
private final Set<String> listOfAuthorizationVariables = new HashSet<>();
public ApexSuggestUsingNamedCredRule() {
super.addRuleChainVisit(ASTUserClass.class);
setProperty(CODECLIMATE_CATEGORIES, new String[] { "Security" });
setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 100);
setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false);
}
@Override
public Object visit(ASTUserClass node, Object data) {
if (Helper.isTestMethodOrClass(node)) {
return data;
}
List<ASTVariableDeclaration> variableDecls = node.findDescendantsOfType(ASTVariableDeclaration.class);
for (ASTVariableDeclaration varDecl : variableDecls) {
findAuthLiterals(varDecl);
}
List<ASTField> fieldDecl = node.findDescendantsOfType(ASTField.class);
for (ASTField fDecl : fieldDecl) {
findFieldLiterals(fDecl);
}
List<ASTMethodCallExpression> methodCalls = node.findDescendantsOfType(ASTMethodCallExpression.class);
for (ASTMethodCallExpression method : methodCalls) {
flagAuthorizationHeaders(method, data);
}
listOfAuthorizationVariables.clear();
return data;
}
private void findFieldLiterals(final ASTField fDecl) {
Object f = fDecl.getNode().getFieldInfo().getValue();
if (f != null && f instanceof String) {
final String fieldValue = (String) f;
if (AUTHORIZATION.equalsIgnoreCase(fieldValue)) {
listOfAuthorizationVariables.add(Helper.getFQVariableName(fDecl));
}
}
}
private void flagAuthorizationHeaders(final ASTMethodCallExpression node, Object data) {
if (!Helper.isMethodName(node, SET_HEADER)) {
return;
}
final ASTBinaryExpression binaryNode = node.getFirstChildOfType(ASTBinaryExpression.class);
if (binaryNode != null) {
runChecks(binaryNode, data);
}
runChecks(node, data);
}
private void findAuthLiterals(final AbstractApexNode<?> node) {
ASTLiteralExpression literal = node.getFirstChildOfType(ASTLiteralExpression.class);
if (literal != null) {
ASTVariableExpression variable = node.getFirstChildOfType(ASTVariableExpression.class);
if (variable != null) {
if (isAuthorizationLiteral(literal)) {
listOfAuthorizationVariables.add(Helper.getFQVariableName(variable));
}
}
}
}
private void runChecks(final AbstractApexNode<?> node, Object data) {
ASTLiteralExpression literalNode = node.getFirstChildOfType(ASTLiteralExpression.class);
if (literalNode != null) {
if (isAuthorizationLiteral(literalNode)) {
addViolation(data, literalNode);
}
}
final ASTVariableExpression varNode = node.getFirstChildOfType(ASTVariableExpression.class);
if (varNode != null) {
if (listOfAuthorizationVariables.contains(Helper.getFQVariableName(varNode))) {
addViolation(data, varNode);
}
}
}
private boolean isAuthorizationLiteral(final ASTLiteralExpression literal) {
Object o = literal.getNode().getLiteral();
if (o instanceof String) {
String lit = (String) o;
if (lit.equalsIgnoreCase(AUTHORIZATION)) {
return true;
}
}
return false;
}
}