/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.apex.rule.security; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import net.sourceforge.pmd.lang.apex.ast.ASTDmlDeleteStatement; import net.sourceforge.pmd.lang.apex.ast.ASTDmlInsertStatement; import net.sourceforge.pmd.lang.apex.ast.ASTDmlMergeStatement; import net.sourceforge.pmd.lang.apex.ast.ASTDmlUndeleteStatement; import net.sourceforge.pmd.lang.apex.ast.ASTDmlUpdateStatement; import net.sourceforge.pmd.lang.apex.ast.ASTDmlUpsertStatement; import net.sourceforge.pmd.lang.apex.ast.ASTDottedExpression; import net.sourceforge.pmd.lang.apex.ast.ASTField; import net.sourceforge.pmd.lang.apex.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.apex.ast.ASTMethodCallExpression; import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; import net.sourceforge.pmd.lang.apex.ast.ASTNewNameValueObjectExpression; import net.sourceforge.pmd.lang.apex.ast.ASTReferenceExpression; import net.sourceforge.pmd.lang.apex.ast.ASTSoqlExpression; import net.sourceforge.pmd.lang.apex.ast.ASTSoslExpression; 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.ApexNode; import apex.jorje.data.ast.Identifier; import apex.jorje.data.ast.TypeRef; import apex.jorje.data.ast.TypeRef.ClassTypeRef; import apex.jorje.semantic.ast.expression.MethodCallExpression; import apex.jorje.semantic.ast.expression.NewNameValueObjectExpression; import apex.jorje.semantic.ast.expression.VariableExpression; import apex.jorje.semantic.ast.member.Field; import apex.jorje.semantic.ast.member.Parameter; import apex.jorje.semantic.ast.statement.FieldDeclaration; import apex.jorje.semantic.ast.statement.VariableDeclaration; /** * Helper methods * * @author sergey.gorbaty * */ public final class Helper { protected static final String ANY_METHOD = "*"; private Helper() { throw new AssertionError("Can't instantiate helper classes"); } static boolean isTestMethodOrClass(final ApexNode<?> node) { final List<ASTModifierNode> modifierNode = node.findChildrenOfType(ASTModifierNode.class); for (final ASTModifierNode m : modifierNode) { if (m.getNode().getModifiers().isTest()) { return true; } } final String className = node.getNode().getDefiningType().getApexName(); if (className.endsWith("Test")) { return true; } return false; } static boolean foundAnySOQLorSOSL(final ApexNode<?> node) { final List<ASTSoqlExpression> dmlSoqlExpression = node.findDescendantsOfType(ASTSoqlExpression.class); final List<ASTSoslExpression> dmlSoslExpression = node.findDescendantsOfType(ASTSoslExpression.class); if (dmlSoqlExpression.isEmpty() && dmlSoslExpression.isEmpty()) { return false; } return true; } /** * Finds DML operations in a given node descendants' path * * @param node * * @return true if found DML operations in node descendants */ static boolean foundAnyDML(final ApexNode<?> node) { final List<ASTDmlUpsertStatement> dmlUpsertStatement = node.findDescendantsOfType(ASTDmlUpsertStatement.class); final List<ASTDmlUpdateStatement> dmlUpdateStatement = node.findDescendantsOfType(ASTDmlUpdateStatement.class); final List<ASTDmlUndeleteStatement> dmlUndeleteStatement = node .findDescendantsOfType(ASTDmlUndeleteStatement.class); final List<ASTDmlMergeStatement> dmlMergeStatement = node.findDescendantsOfType(ASTDmlMergeStatement.class); final List<ASTDmlInsertStatement> dmlInsertStatement = node.findDescendantsOfType(ASTDmlInsertStatement.class); final List<ASTDmlDeleteStatement> dmlDeleteStatement = node.findDescendantsOfType(ASTDmlDeleteStatement.class); if (dmlUpsertStatement.isEmpty() && dmlUpdateStatement.isEmpty() && dmlUndeleteStatement.isEmpty() && dmlMergeStatement.isEmpty() && dmlInsertStatement.isEmpty() && dmlDeleteStatement.isEmpty()) { return false; } return true; } static boolean isMethodName(final ASTMethodCallExpression methodNode, final String className, final String methodName) { final ASTReferenceExpression reference = methodNode.getFirstChildOfType(ASTReferenceExpression.class); if (reference.getNode().getJadtIdentifiers().size() == 1) { if (reference.getNode().getJadtIdentifiers().get(0).value.equalsIgnoreCase(className)) { if (methodName.equals(ANY_METHOD) || isMethodName(methodNode, methodName)) { return true; } } } return false; } static boolean isMethodName(final ASTMethodCallExpression m, final String methodName) { return isMethodName(m.getNode(), methodName); } static boolean isMethodName(final MethodCallExpression m, final String methodName) { return m.getMethodName().equalsIgnoreCase(methodName); } static boolean isMethodCallChain(final ASTMethodCallExpression methodNode, final String... methodNames) { String methodName = methodNames[methodNames.length - 1]; if (Helper.isMethodName(methodNode, methodName)) { final ASTReferenceExpression reference = methodNode.getFirstChildOfType(ASTReferenceExpression.class); if (reference != null) { final ASTDottedExpression dottedExpression = reference.getFirstChildOfType(ASTDottedExpression.class); if (dottedExpression != null) { final ASTMethodCallExpression nestedMethod = dottedExpression .getFirstChildOfType(ASTMethodCallExpression.class); if (nestedMethod != null) { String[] newMethodNames = Arrays.copyOf(methodNames, methodNames.length - 1); return isMethodCallChain(nestedMethod, newMethodNames); } else { String[] newClassName = Arrays.copyOf(methodNames, methodNames.length - 1); if (newClassName.length == 1) { return Helper.isMethodName(methodNode, newClassName[0], methodName); } } } } } return false; } static String getFQVariableName(final ASTVariableExpression variable) { final ASTReferenceExpression ref = variable.getFirstChildOfType(ASTReferenceExpression.class); String objectName = ""; if (ref != null) { if (ref.getNode().getJadtIdentifiers().size() == 1) { objectName = ref.getNode().getJadtIdentifiers().get(0).value + "."; } } VariableExpression n = variable.getNode(); StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":").append(objectName) .append(n.getIdentifier().value); return sb.toString(); } static String getFQVariableName(final ASTVariableDeclaration variable) { VariableDeclaration n = variable.getNode(); StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":") .append(n.getLocalInfo().getName()); return sb.toString(); } static String getFQVariableName(final ASTField variable) { Field n = variable.getNode(); StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":") .append(n.getFieldInfo().getName()); return sb.toString(); } static String getVariableType(final ASTField variable) { Field n = variable.getNode(); StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":") .append(n.getFieldInfo().getName()); return sb.toString(); } static String getFQVariableName(final ASTFieldDeclaration variable) { FieldDeclaration n = variable.getNode(); String name = ""; try { java.lang.reflect.Field f = n.getClass().getDeclaredField("name"); f.setAccessible(true); Identifier nameField = (Identifier) f.get(n); name = nameField.value; } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { } StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":").append(name); return sb.toString(); } static String getFQVariableName(final ASTNewNameValueObjectExpression variable) { NewNameValueObjectExpression n = variable.getNode(); String objType = ""; try { // no other way to get this field, Apex Jorje does not expose it java.lang.reflect.Field f = n.getClass().getDeclaredField("typeRef"); f.setAccessible(true); ClassTypeRef hiddenField = (ClassTypeRef) f.get(n); objType = hiddenField.className.get(0).value; } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { } StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":").append(objType); return sb.toString(); } static boolean isSystemLevelClass(ASTUserClass node) { List<TypeRef> interfaces = node.getNode().getDefiningType().getCodeUnitDetails().getInterfaceTypeRefs(); for (TypeRef intObject : interfaces) { try { java.lang.reflect.Field f = intObject.getClass().getDeclaredField("className"); f.setAccessible(true); @SuppressWarnings("unchecked") ArrayList<Identifier> ids = (ArrayList<Identifier>) f.get(intObject); if (isWhitelisted(ids)) { return true; } } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { } } return false; } private static boolean isWhitelisted(List<Identifier> ids) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < ids.size(); i++) { sb.append(ids.get(i).value); if (i != (ids.size() - 1)) { sb.append("."); } } switch (sb.toString().toLowerCase()) { case "queueable": case "database.batchable": case "installhandler": return true; default: break; } return false; } public static String getFQVariableName(Parameter p) { StringBuffer sb = new StringBuffer(); sb.append(p.getDefiningType()).append(":").append(p.getName().value); return sb.toString(); } }