/* Copyright 2012-2015 SAP SE * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package eu.aniketos.securebpmn.ntk; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.ArrayList; import java.util.List; import org.eclipse.bpmn2.Bpmn2Factory; import org.eclipse.bpmn2.DataInput; import org.eclipse.bpmn2.DataOutput; import org.eclipse.bpmn2.FormProperty; import org.eclipse.bpmn2.InputOutputSpecification; import org.eclipse.bpmn2.InputSet; import org.eclipse.bpmn2.OutputSet; import org.eclipse.bpmn2.ServiceTask; import org.eclipse.bpmn2.Task; import org.eclipse.bpmn2.UserTask; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.emf.ecore.EObject; import org.eclipse.graphiti.mm.pictograms.Diagram; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.securebpmn2.AuthorizationConstraint; import org.eclipse.securebpmn2.ItemAwareElementAction; import org.eclipse.securebpmn2.NeedToKnow; import org.eclipse.securebpmn2.Permission; import eu.aniketos.securebpmn.validation.ProcVarAccess; import eu.aniketos.securebpmn.validation.ProcVarListASTVisitor; /** * This class contains utility methods that are being used in the need-to-know * analysis. * * */ public class NeedToKnowUtil { public static final String ID_PREFIX_IOSPEC = "iospec_"; public static final String ID_PREFIX_INSET = "inset_ntk_"; public static final String ID_PREFIX_OUTSET = "outset_ntk_"; public static final String ID_PREFIX_INPUT = "input_ntk_"; public static final String ID_PREFIX_OUTPUT = "output_ntk_"; /** * Provides the names of the SecureBPMN ItemAwareElementActions that are * used. * * @return An Array containing the names of the ItemAwareElementActions as * Strings. */ public static String[] getItemAwareElementActionNames() { return new String[] { "read", "write", "read/write" }; } /** * Finds the names of the process variables that are accessed in the * provided task. Only the HTML forms of a UserTask and the Java * implementation of a ServiceTask can be analyzed. * * @param task * The task (UserTask or ServiceTask) that should be analyzed. * @return A List with the names of the accessed process variables as * Strings. */ public static List<String> getAccessedProcessVariableNames(Task task) { List<String> result = new ArrayList<String>(); List<ProcVarAccess> varAccesses = new ArrayList<ProcVarAccess>(); if (task instanceof UserTask) { varAccesses = getAccessedProcessVariables((UserTask) task); } else if (task instanceof ServiceTask) { varAccesses = getAccessedProcessVariables((ServiceTask) task); } for (ProcVarAccess var : varAccesses) { final String varName = var.getName(); if (!result.contains(varName)) result.add(varName); } return result; } /** * Analyzes a UserTask and extracts the process variables that are accesses * and the type of access (read, write or both). Note that only the Activiti * built-in HTML forms are analyzed. * * @param userTask * The UserTask that should be analyzed. * @return A List containing the process variable accesses represented as * ProcVarAccess. */ public static List<ProcVarAccess> getAccessedProcessVariables( UserTask userTask) { List<ProcVarAccess> result = new ArrayList<ProcVarAccess>(); for (FormProperty fp : userTask.getFormProperties()) { boolean var = false; String expr = fp.getValue(); if (expr.startsWith("${") && expr.endsWith("}")) { expr = expr.substring(2, expr.length() - 1); var = true; } if (var) { // variable access if (fp.getReadable() != null && fp.getReadable() && fp.getWriteable() != null && !fp.getWriteable()) { // read only result.add(new ProcVarAccess(expr, -1, false)); } else if (fp.getReadable() != null && !fp.getReadable() && fp.getWriteable() != null && fp.getWriteable()) { // write only result.add(new ProcVarAccess(expr, -1, true)); } else { // read/write default assumption result.add(new ProcVarAccess(expr, -1, false)); result.add(new ProcVarAccess(expr, -1, true)); } } } return result; } /** * Analyzes a ServiceTask and extracts the process variables that are * accesses and the type of access (read, write or both). Note that only * ServiceTasks with a Java implementation are analyzed. * * @param serviceTask * The ServiceTask that should be analyzed. * @return A List containing the process variable accesses represented as * ProcVarAccess. */ public static List<ProcVarAccess> getAccessedProcessVariables( ServiceTask serviceTask) { List<ProcVarAccess> result = new ArrayList<ProcVarAccess>(); // check if service task has valid java impl if (serviceTask.getImplementationType() == null || !(serviceTask.getImplementationType().equals("classType"))) { return result; } if (serviceTask.getImplementation() == null || serviceTask.getImplementation().length() == 0) { return result; } final String canonicalClassName = serviceTask.getImplementation(); String[] classParts = canonicalClassName.split("\\."); String implFileName = classParts[classParts.length - 1] + ".java"; StringBuilder sb = new StringBuilder(); sb.append("/"); sb.append("src"); sb.append("/"); sb.append("main"); sb.append("/"); sb.append("java"); sb.append("/"); for (int i = 0; i < classParts.length - 1; i++) { sb.append(classParts[i]); sb.append("/"); } sb.append(implFileName); IResource resourceToRead = null; int filesFound = 0; IWorkspace workspace = ResourcesPlugin.getWorkspace(); IWorkspaceRoot root = workspace.getRoot(); IProject[] projects = root.getProjects(); for (IProject project : projects) { IResource resourceInRuntimeWorkspace = root.findMember(project .getName() + sb.toString()); if (resourceInRuntimeWorkspace != null) { filesFound++; if (resourceToRead == null) { resourceToRead = resourceInRuntimeWorkspace; } } } if (filesFound == 0) { return result; } else if (filesFound > 1) { System.err .println("[ListProcVar WARNING] Found multiple source files for class: " + canonicalClassName); } // read file StringBuffer fileData = new StringBuffer(1000); try { BufferedReader reader = new BufferedReader(new FileReader(new File( resourceToRead.getLocationURI()))); char[] buf = new char[1024]; int numRead = 0; while ((numRead = reader.read(buf)) != -1) { String readData = String.valueOf(buf, 0, numRead); fileData.append(readData); buf = new char[1024]; } reader.close(); } catch (Exception e) { System.err .println("[ListProcVar ERROR] Error reading source file for class \"" + canonicalClassName + "\": " + e.getMessage()); return result; } // build AST for Impl ASTParser parser = ASTParser.newParser(AST.JLS3); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setSource(fileData.toString().toCharArray()); CompilationUnit node = (CompilationUnit) parser.createAST(null); node.accept(new ProcVarListASTVisitor(result)); return result; } /** * Updates the BPMN 2.0 elements that represent the process variable access * of a given task, in particular, DataInput and DataOutput elements. * * @param task * The task whose elements should be updated. * @param diagram * The diagram containing the task. */ public static void updateIOSpecification(Task task, Diagram diagram) { if (!(task instanceof UserTask || task instanceof ServiceTask)) return; InputOutputSpecification iospec = task.getIoSpecification(); // create InputOutputSpecification if missing if (iospec == null) { iospec = Bpmn2Factory.eINSTANCE.createInputOutputSpecification(); iospec.setId(ID_PREFIX_IOSPEC + task.getId()); task.setIoSpecification(iospec); diagram.eResource().getContents().add(iospec); } // keep InputOutputSpecification ID consistent with Task ID if (!iospec.getId().equals(ID_PREFIX_IOSPEC + task.getId())) { task.getIoSpecification().setId(ID_PREFIX_IOSPEC + task.getId()); } // search for NtK InputSet InputSet inSet = null; for (InputSet tmpInSet : iospec.getInputSets()) { if (tmpInSet.getId().equals(ID_PREFIX_INSET + task.getId())) { inSet = tmpInSet; break; } } if (inSet == null) { inSet = Bpmn2Factory.eINSTANCE.createInputSet(); inSet.setId(ID_PREFIX_INSET + task.getId()); inSet.setName("Need-to-know DataObjects read by task " + task.getId()); iospec.getInputSets().add(inSet); diagram.eResource().getContents().add(inSet); } // search for NtK OutputSet OutputSet outSet = null; for (OutputSet tmpOutSet : iospec.getOutputSets()) { if (tmpOutSet.getId().equals(ID_PREFIX_OUTSET + task.getId())) { outSet = tmpOutSet; break; } } if (outSet == null) { outSet = Bpmn2Factory.eINSTANCE.createOutputSet(); outSet.setId(ID_PREFIX_OUTSET + task.getId()); outSet.setName("Need-to-know DataObjects written by task " + task.getId()); iospec.getOutputSets().add(outSet); diagram.eResource().getContents().add(outSet); } // create DataInput/DataOutput elements List<ProcVarAccess> varAccesses = new ArrayList<ProcVarAccess>(); if (task instanceof UserTask) { varAccesses = getAccessedProcessVariables((UserTask) task); } else { varAccesses = getAccessedProcessVariables((ServiceTask) task); } // add new elements List<EObject> diagramElements = diagram.eResource().getContents(); for (ProcVarAccess varAccess : varAccesses) { if (varAccess.isWrite()) { // check if DataOutput element exists in diagram DataOutput output = null; for (EObject obj : diagramElements) { if (obj instanceof DataOutput) { if (((DataOutput) obj).getId().equals( ID_PREFIX_OUTPUT + varAccess.getName())) { output = (DataOutput) obj; break; } } } if (output == null) { // DataOutput does not exist, create output = Bpmn2Factory.eINSTANCE.createDataOutput(); output.setId(ID_PREFIX_OUTPUT + varAccess.getName()); outSet.getDataOutputRefs().add(output); output.getOutputSetRefs().add(outSet); diagram.eResource().getContents().add(output); } else { // check if DataOutput element exists in OutputSet if (!outSet.getDataOutputRefs().contains(output)) { outSet.getDataOutputRefs().add(output); output.getOutputSetRefs().add(outSet); } } } else { // check if DataInput exists in diagram DataInput input = null; for (EObject obj : diagramElements) { if (obj instanceof DataInput) { if (((DataInput) obj).getId().equals( ID_PREFIX_INPUT + varAccess.getName())) { input = (DataInput) obj; break; } } } if (input == null) { // DataInput does not exist, create input = Bpmn2Factory.eINSTANCE.createDataInput(); input.setId(ID_PREFIX_INPUT + varAccess.getName()); inSet.getDataInputRefs().add(input); input.getInputSetRefs().add(inSet); diagram.eResource().getContents().add(input); } else { // check if DataInput element exists in InputSet if (!inSet.getDataInputRefs().contains(input)) { inSet.getDataInputRefs().add(input); input.getInputSetRefs().add(inSet); } } } } // remove elements for missing process variables for (DataInput in : inSet.getDataInputRefs()) { boolean varDeleted = true; for (ProcVarAccess pvar : varAccesses) { if (in.getId().equals(ID_PREFIX_INPUT + pvar.getName()) && !pvar.isWrite()) { varDeleted = false; break; } } if (varDeleted) { inSet.getDataInputRefs().remove(in); in.getInputSetRefs().remove(inSet); if (in.getInputSetRefs().size() == 0) { diagram.eResource().getContents().remove(in); } for (ItemAwareElementAction iaea : in .getItemAwareElementActions()) { for (Permission p : iaea.getPermissions()) { p.getActions().remove(iaea); } } } } for (DataOutput out : outSet.getDataOutputRefs()) { boolean varDeleted = true; for (ProcVarAccess pvar : varAccesses) { if (out.getId().equals(ID_PREFIX_OUTPUT + pvar.getName()) && pvar.isWrite()) { varDeleted = false; break; } } if (varDeleted) { outSet.getDataOutputRefs().remove(out); out.getOutputSetRefs().remove(outSet); if (out.getOutputSetRefs().size() == 0) { diagram.eResource().getContents().remove(out); } for (ItemAwareElementAction iaea : out .getItemAwareElementActions()) { for (Permission p : iaea.getPermissions()) { p.getActions().remove(iaea); } } } } // remove broken NtK permissions for (EObject o : diagramElements) { if (o instanceof NeedToKnow) { NeedToKnow ntk = (NeedToKnow) o; if (ntk.getActions().size() == 0) { // delete ActivityAC for (AuthorizationConstraint ac : ntk .getAuthorizationConstraints()) { diagram.eResource().getContents().remove(ac); } // delete NtK diagram.eResource().getContents().remove(ntk); } } } } }