Autopsy  4.19.3
Graphical digital forensics platform for The Sleuth Kit and other tools.
CommunicationsGraph.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.communications;
20 
21 import com.github.mustachejava.DefaultMustacheFactory;
22 import com.github.mustachejava.Mustache;
23 import com.google.common.collect.Multimap;
24 import com.google.common.collect.MultimapBuilder;
25 import com.mxgraph.model.mxCell;
26 import com.mxgraph.model.mxICell;
27 import com.mxgraph.util.mxConstants;
28 import com.mxgraph.view.mxGraph;
29 import com.mxgraph.view.mxStylesheet;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.io.StringWriter;
33 import java.net.URL;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.concurrent.CancellationException;
40 import java.util.concurrent.ExecutionException;
41 import java.util.logging.Level;
42 import javax.swing.SwingWorker;
45 import org.sleuthkit.datamodel.AccountDeviceInstance;
46 import org.sleuthkit.datamodel.AccountPair;
47 import org.sleuthkit.datamodel.CommunicationsFilter;
48 import org.sleuthkit.datamodel.CommunicationsManager;
49 import org.sleuthkit.datamodel.Content;
50 import org.sleuthkit.datamodel.TskCoreException;
51 
56 final class CommunicationsGraph extends mxGraph {
57 
58  private static final Logger logger = Logger.getLogger(CommunicationsGraph.class.getName());
59  private static final URL MARKER_PIN_URL = CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/marker--pin.png");
60  private static final URL LOCK_URL = CommunicationsGraph.class.getResource("/org/sleuthkit/autopsy/communications/images/lock_large_locked.png");
61 
62  /* mustache.java template */
63  private final static Mustache labelMustache;
64 
65  static {
66  final InputStream templateStream = CommunicationsGraph.class.getResourceAsStream("/org/sleuthkit/autopsy/communications/Vertex_Label_template.html");
67  labelMustache = new DefaultMustacheFactory().compile(new InputStreamReader(templateStream), "Vertex_Label");
68  }
69 
70  /* Style sheet for default vertex and edge styles. These are initialized in
71  * the static block below. */
72  static final private mxStylesheet mxStylesheet = new mxStylesheet();
73 
74  static {
75  //initialize defaul vertex properties
76  mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
77  mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_PERIMETER, mxConstants.PERIMETER_ELLIPSE);
78  mxStylesheet.getDefaultVertexStyle().put(mxConstants.STYLE_FONTCOLOR, "000000");
79 
80  //initialize defaul edge properties
81  mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_NOLABEL, true);
82  mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_PERIMETER_SPACING, 0);
83  mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_ENDARROW, mxConstants.NONE);
84  mxStylesheet.getDefaultEdgeStyle().put(mxConstants.STYLE_STARTARROW, mxConstants.NONE);
85  }
86 
88  private final Map<String, mxCell> nodeMap = new HashMap<>();
89 
91  private final Multimap<Content, mxCell> edgeMap = MultimapBuilder.hashKeys().hashSetValues().build();
92  private final LockedVertexModel lockedVertexModel;
93 
94  private final PinnedAccountModel pinnedAccountModel;
95 
96  CommunicationsGraph(PinnedAccountModel pinnedAccountModel, LockedVertexModel lockedVertexModel) {
97  super(mxStylesheet);
98  this.pinnedAccountModel = pinnedAccountModel;
99  this.lockedVertexModel = lockedVertexModel;
100  //set fixed properties of graph.
101  setAutoSizeCells(true);
102  setCellsCloneable(false);
103  setDropEnabled(false);
104  setCellsCloneable(false);
105  setCellsEditable(false);
106  setCellsResizable(false);
107  setCellsMovable(true);
108  setCellsDisconnectable(false);
109  setConnectableEdges(false);
110  setDisconnectOnMove(false);
111  setEdgeLabelsMovable(false);
112  setVertexLabelsMovable(false);
113  setAllowDanglingEdges(false);
114  setCellsBendable(true);
115  setKeepEdgesInBackground(true);
116  setResetEdgesOnMove(true);
117  setHtmlLabels(true);
118  }
119 
125  LockedVertexModel getLockedVertexModel() {
126  return lockedVertexModel;
127  }
128 
129  PinnedAccountModel getPinnedAccountModel() {
130  return pinnedAccountModel;
131  }
132 
133  void clear() {
134  nodeMap.clear();
135  edgeMap.clear();
136  removeCells(getChildVertices(getDefaultParent()));
137  }
138 
139  @Override
140  public String convertValueToString(Object cell) {
141  final StringWriter stringWriter = new StringWriter();
142  HashMap<String, Object> scopes = new HashMap<>();
143 
144  Object value = getModel().getValue(cell);
145  if (value instanceof AccountDeviceInstanceKey) {
146  final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
147 
148  scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
149  scopes.put("size", Math.round(Math.log(adiKey.getMessageCount()) + 5));
150  scopes.put("iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
151  scopes.put("pinned", pinnedAccountModel.isAccountPinned(adiKey.getAccountDeviceInstance()));
152  scopes.put("MARKER_PIN_URL", MARKER_PIN_URL);
153  scopes.put("locked", lockedVertexModel.isVertexLocked((mxCell) cell));
154  scopes.put("LOCK_URL", LOCK_URL);
155 
156  labelMustache.execute(stringWriter, scopes);
157 
158  return stringWriter.toString();
159  } else {
160  return "";
161  }
162  }
163 
164  @Override
165  public String getToolTipForCell(Object cell) {
166  final StringWriter stringWriter = new StringWriter();
167  HashMap<String, Object> scopes = new HashMap<>();
168 
169  Object value = getModel().getValue(cell);
170  if (value instanceof AccountDeviceInstanceKey) {
171  final AccountDeviceInstanceKey adiKey = (AccountDeviceInstanceKey) value;
172 
173  scopes.put("accountName", adiKey.getAccountDeviceInstance().getAccount().getTypeSpecificID());
174  scopes.put("relationships", 12);// Math.round(Math.log(adiKey.getMessageCount()) + 5));
175  scopes.put("iconFileName", CommunicationsGraph.class.getResource(Utils.getIconFilePath(adiKey.getAccountDeviceInstance().getAccount().getAccountType())));
176  scopes.put("pinned", pinnedAccountModel.isAccountPinned(adiKey.getAccountDeviceInstance()));
177  scopes.put("MARKER_PIN_URL", MARKER_PIN_URL);
178  scopes.put("locked", lockedVertexModel.isVertexLocked((mxCell) cell));
179  scopes.put("LOCK_URL", LOCK_URL);
180  scopes.put("device_id", adiKey.getAccountDeviceInstance().getDeviceId());
181 
182  labelMustache.execute(stringWriter, scopes);
183 
184  return stringWriter.toString();
185  } else {
186  final mxICell edge = (mxICell) cell;
187  final long count = (long) edge.getValue();
188  return "<html>" + edge.getId() + "<br>" + count + (count == 1 ? " relationship" : " relationships") + "</html>";
189  }
190  }
191 
192  SwingWorker<?, ?> rebuild(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
193  return new RebuildWorker(progress, commsManager, currentFilter);
194  }
195 
196  void resetGraph() {
197  clear();
198  getView().setScale(1);
199  pinnedAccountModel.clear();
200  lockedVertexModel.clear();
201  }
202 
203  private mxCell getOrCreateVertex(AccountDeviceInstance adi, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
204  final AccountDeviceInstance accountDeviceInstance = adi;
205  final String name = accountDeviceInstance.getAccount().getTypeSpecificID();
206 
207  final mxCell vertex = nodeMap.computeIfAbsent(name + accountDeviceInstance.getDeviceId(), vertexName -> {
208  long adiRelationshipsCount = 1;
209  try {
210  adiRelationshipsCount = commsManager.getRelationshipSourcesCount(accountDeviceInstance, currentFilter);
211  } catch (TskCoreException tskCoreException) {
212  logger.log(Level.SEVERE, "There was an error fetching relationships for the node: " + accountDeviceInstance, tskCoreException);
213  }
214 
215  double size = Math.sqrt(adiRelationshipsCount) + 10;
216  AccountDeviceInstanceKey adiKey = new AccountDeviceInstanceKey(adi, currentFilter, adiRelationshipsCount);
217 
218  mxCell newVertex = (mxCell) insertVertex(
219  getDefaultParent(),
220  name, adiKey,
221  Math.random() * 400,
222  Math.random() * 400,
223  size,
224  size);
225  return newVertex;
226  });
227  return vertex;
228  }
229 
230  @SuppressWarnings("unchecked")
231  private mxCell addOrUpdateEdge(long relSources,
232  AccountDeviceInstance account1, AccountDeviceInstance account2,
233  CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
234  mxCell vertex1 = getOrCreateVertex(account1, commsManager, currentFilter);
235  mxCell vertex2 = getOrCreateVertex(account2, commsManager, currentFilter);
236  Object[] edgesBetween = getEdgesBetween(vertex1, vertex2);
237  mxCell edge;
238  if (edgesBetween.length == 0) {
239  final String edgeName = vertex1.getId() + " - " + vertex2.getId();
240  edge = (mxCell) insertEdge(getDefaultParent(), edgeName, relSources, vertex1, vertex2,
241  "strokeWidth=" + (Math.log(relSources) + 1));
242  } else {
243  edge = (mxCell) edgesBetween[0];
244  edge.setStyle("strokeWidth=" + (Math.log(relSources) + 1));
245  }
246  return edge;
247  }
248 
253  private class RebuildWorker extends SwingWorker<Void, Void> {
254 
256  private final CommunicationsManager commsManager;
257  private final CommunicationsFilter currentFilter;
258 
259  RebuildWorker(ProgressIndicator progress, CommunicationsManager commsManager, CommunicationsFilter currentFilter) {
260  this.progressIndicator = progress;
261  this.currentFilter = currentFilter;
262  this.commsManager = commsManager;
263 
264  }
265 
266  @Override
267  protected Void doInBackground() {
268  progressIndicator.start("Loading accounts");
269  int progressCounter = 0;
270  try {
274  final Set<AccountDeviceInstance> relatedAccounts = new HashSet<>();
275  for (final AccountDeviceInstance adi : pinnedAccountModel.getPinnedAccounts()) {
276  if (isCancelled()) {
277  break;
278  }
279  //get accounts related to pinned account
280  final List<AccountDeviceInstance> relatedAccountDeviceInstances
281  = commsManager.getRelatedAccountDeviceInstances(adi, currentFilter);
282  relatedAccounts.add(adi);
283  getOrCreateVertex(adi, commsManager, currentFilter);
284 
285  for (final AccountDeviceInstance relatedADI : relatedAccountDeviceInstances)
286  relatedAccounts.add(relatedADI);
287 
288  progressIndicator.progress(++progressCounter);
289  }
290 
291  Map<AccountPair, Long> relationshipCounts = commsManager.getRelationshipCountsPairwise(relatedAccounts, currentFilter);
292 
293  int total = relationshipCounts.size();
294  int progress = 0;
295  String progressText = "";
296  progressIndicator.switchToDeterminate("", 0, total);
297  for (Map.Entry<AccountPair, Long> entry : relationshipCounts.entrySet()) {
298  Long count = entry.getValue();
299  AccountPair relationshipKey = entry.getKey();
300  AccountDeviceInstance account1 = relationshipKey.getFirst();
301  AccountDeviceInstance account2 = relationshipKey.getSecond();
302 
303  if (pinnedAccountModel.isAccountPinned(account1)
304  || pinnedAccountModel.isAccountPinned(account2)) {
305  mxCell addEdge = addOrUpdateEdge(count, account1, account2, commsManager, currentFilter);
306  progressText = addEdge.getId();
307  }
308  progressIndicator.progress(progressText, progress++);
309  }
310  } catch (TskCoreException tskCoreException) {
311  logger.log(Level.SEVERE, "Error", tskCoreException);
312  }
313 
314  return null;
315  }
316 
317  @Override
318  protected void done() {
319  super.done();
320  try {
321  get();
322  } catch (InterruptedException | ExecutionException ex) {
323  logger.log(Level.SEVERE, "Error building graph visualization. ", ex);
324  } catch (CancellationException ex) {
325  logger.log(Level.INFO, "Graph visualization cancelled");
326  } finally {
327  progressIndicator.finish();
328  }
329  }
330  }
331 }
void start(String message, int totalWorkUnits)
void switchToDeterminate(String message, int workUnitsCompleted, int totalWorkUnits)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.