/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.apex.rule.security;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sourceforge.pmd.lang.apex.ast.ASTAssignmentExpression;
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.ASTMethod;
import net.sourceforge.pmd.lang.apex.ast.ASTNewObjectExpression;
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.ast.ApexNode;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
import apex.jorje.data.ast.Identifier;
import apex.jorje.data.ast.TypeRef.ClassTypeRef;
import apex.jorje.semantic.symbol.member.variable.StandardFieldInfo;
/**
* Looking for potential Open redirect via PageReference variable input
*
* @author sergey.gorbaty
*/
public class ApexOpenRedirectRule extends AbstractApexRule {
private static final String PAGEREFERENCE = "PageReference";
private final Set<String> listOfStringLiteralVariables = new HashSet<>();
public ApexOpenRedirectRule() {
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) || Helper.isSystemLevelClass(node)) {
return data; // stops all the rules
}
List<ASTAssignmentExpression> assignmentExprs = node.findDescendantsOfType(ASTAssignmentExpression.class);
for (ASTAssignmentExpression assignment : assignmentExprs) {
findSafeLiterals(assignment);
}
List<ASTVariableDeclaration> variableDecls = node.findDescendantsOfType(ASTVariableDeclaration.class);
for (ASTVariableDeclaration varDecl : variableDecls) {
findSafeLiterals(varDecl);
}
List<ASTField> fieldDecl = node.findDescendantsOfType(ASTField.class);
for (ASTField fDecl : fieldDecl) {
findSafeLiterals(fDecl);
}
List<ASTNewObjectExpression> newObjects = node.findDescendantsOfType(ASTNewObjectExpression.class);
for (ASTNewObjectExpression newObj : newObjects) {
checkNewObjects(newObj, data);
}
listOfStringLiteralVariables.clear();
return data;
}
private void findSafeLiterals(AbstractApexNode<?> node) {
ASTBinaryExpression binaryExp = node.getFirstChildOfType(ASTBinaryExpression.class);
if (binaryExp != null) {
findSafeLiterals(binaryExp);
}
ASTLiteralExpression literal = node.getFirstChildOfType(ASTLiteralExpression.class);
if (literal != null) {
int index = literal.jjtGetChildIndex();
if (index == 0) {
if (node instanceof ASTVariableDeclaration) {
addVariable((ASTVariableDeclaration) node);
} else if (node instanceof ASTBinaryExpression) {
ASTVariableDeclaration parent = node.getFirstParentOfType(ASTVariableDeclaration.class);
if (parent != null) {
addVariable(parent);
}
ASTAssignmentExpression assignment = node.getFirstParentOfType(ASTAssignmentExpression.class);
if (assignment != null) {
ASTVariableExpression var = assignment.getFirstChildOfType(ASTVariableExpression.class);
if (var != null) {
addVariable(var);
}
}
}
}
} else {
if (node instanceof ASTField) {
/*
* sergey.gorbaty: Apex Jorje parser is returning a null from
* Field.getFieldInfo(), but the info is available from an inner
* field. DO NOT attempt to optimize this block without checking
* that Jorje parser actually fixed its bug.
*
*/
try {
final Field f = node.getNode().getClass().getDeclaredField("fieldInfo");
f.setAccessible(true);
final StandardFieldInfo fieldInfo = (StandardFieldInfo) f.get(node.getNode());
if (fieldInfo.getType().getApexName().equalsIgnoreCase("String")) {
if (fieldInfo.getValue() != null) {
addVariable(fieldInfo);
}
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException
| IllegalAccessException e) {
// preventing exceptions from this code
}
}
}
}
private void addVariable(StandardFieldInfo fieldInfo) {
StringBuilder sb = new StringBuilder().append(fieldInfo.getDefiningType().getApexName()).append(":")
.append(fieldInfo.getName());
listOfStringLiteralVariables.add(sb.toString());
}
private void addVariable(ASTVariableDeclaration node) {
ASTVariableExpression variable = node.getFirstChildOfType(ASTVariableExpression.class);
addVariable(variable);
}
private void addVariable(ASTVariableExpression node) {
if (node != null) {
listOfStringLiteralVariables.add(Helper.getFQVariableName(node));
}
}
/**
* Traverses all new declarations to find PageReferences
*
* @param node
* @param data
*/
private void checkNewObjects(ASTNewObjectExpression node, Object data) {
ASTMethod method = node.getFirstParentOfType(ASTMethod.class);
if (method != null && Helper.isTestMethodOrClass(method)) {
return;
}
ClassTypeRef classRef = (ClassTypeRef) node.getNode().getTypeRef();
Identifier identifier = classRef.className.get(0);
if (identifier.value.equalsIgnoreCase(PAGEREFERENCE)) {
getObjectValue(node, data);
}
}
/**
* Finds any variables being present in PageReference constructor
*
* @param node
* - PageReference
* @param data
*
*/
private void getObjectValue(ApexNode<?> node, Object data) {
// PageReference(foo);
final List<ASTVariableExpression> variableExpressions = node.findChildrenOfType(ASTVariableExpression.class);
for (ASTVariableExpression variable : variableExpressions) {
if (variable.jjtGetChildIndex() == 0
&& !listOfStringLiteralVariables.contains(Helper.getFQVariableName(variable))) {
addViolation(data, variable);
}
}
// PageReference(foo + bar)
final List<ASTBinaryExpression> binaryExpressions = node.findChildrenOfType(ASTBinaryExpression.class);
for (ASTBinaryExpression z : binaryExpressions) {
getObjectValue(z, data);
}
}
}