Autopsy  4.19.2
Graphical digital forensics platform for The Sleuth Kit and other tools.
Accounts.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2021 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.datamodel.accounts;
20 
21 import com.google.common.collect.Range;
22 import com.google.common.collect.RangeMap;
23 import com.google.common.collect.TreeRangeMap;
24 import com.google.common.eventbus.EventBus;
25 import com.google.common.eventbus.Subscribe;
26 import java.awt.event.ActionEvent;
27 import java.beans.PropertyChangeEvent;
28 import java.beans.PropertyChangeListener;
29 import java.sql.ResultSet;
30 import java.sql.SQLException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.EnumSet;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Objects;
41 import java.util.Optional;
42 import java.util.Set;
43 import java.util.function.Function;
44 import java.util.logging.Level;
45 import java.util.stream.Collectors;
46 import java.util.stream.Stream;
47 import javax.annotation.Nonnull;
48 import javax.annotation.concurrent.Immutable;
49 import javax.swing.AbstractAction;
50 import javax.swing.Action;
51 import javax.swing.SwingUtilities;
52 import org.apache.commons.lang3.StringUtils;
53 import org.openide.nodes.ChildFactory;
54 import org.openide.nodes.Children;
55 import org.openide.nodes.Node;
56 import org.openide.nodes.NodeNotFoundException;
57 import org.openide.nodes.NodeOp;
58 import org.openide.nodes.Sheet;
59 import org.openide.util.NbBundle;
60 import org.openide.util.Utilities;
61 import org.openide.util.WeakListeners;
62 import org.openide.util.lookup.Lookups;
79 import org.sleuthkit.datamodel.AbstractFile;
80 import org.sleuthkit.datamodel.Account;
81 import org.sleuthkit.datamodel.BlackboardArtifact;
82 import org.sleuthkit.datamodel.BlackboardArtifact.Type;
83 import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ACCOUNT;
84 import org.sleuthkit.datamodel.BlackboardAttribute;
85 import org.sleuthkit.datamodel.Content;
86 import org.sleuthkit.datamodel.DataArtifact;
87 import org.sleuthkit.datamodel.SleuthkitCase;
88 import org.sleuthkit.datamodel.TskCoreException;
89 import org.sleuthkit.datamodel.TskData.DbType;
90 
95 final public class Accounts implements AutopsyVisitableItem {
96 
97  private static final Logger LOGGER = Logger.getLogger(Accounts.class.getName());
98  private static final String ICON_BASE_PATH = "/org/sleuthkit/autopsy/images/"; //NON-NLS
101  private static final String DISPLAY_NAME = Bundle.Accounts_RootNode_displayName();
102 
103  @NbBundle.Messages("AccountsRootNode.name=Accounts") //used for the viewArtifact navigation
104  final public static String NAME = Bundle.AccountsRootNode_name();
105 
106  private SleuthkitCase skCase;
107  private final long filteringDSObjId; // 0 if not filtering/grouping by data source
108 
109  private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus");
110 
111  /*
112  * Should rejected accounts be shown in the accounts section of the tree.
113  */
114  private boolean showRejected = false; //NOPMD redundant initializer
115 
118 
119  // tracks the number of each account type found
121 
127  public Accounts(SleuthkitCase skCase) {
128  this(skCase, 0);
129  }
130 
137  public Accounts(SleuthkitCase skCase, long objId) {
138  this.skCase = skCase;
139  this.filteringDSObjId = objId;
140 
141  this.rejectActionInstance = new RejectAccounts();
142  this.approveActionInstance = new ApproveAccounts();
143  this.accountTypeResults = new AccountTypeResults();
144  }
145 
146  @Override
147  public <T> T accept(AutopsyItemVisitor<T> visitor) {
148  return visitor.visit(this);
149  }
150 
159  return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS
160  }
161 
168  private String getFilterByDataSourceClause() {
169  if (filteringDSObjId > 0) {
170  return " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId + " ";
171  }
172 
173  return " ";
174  }
175 
183  @Deprecated
184  public Action newToggleShowRejectedAction() {
185  return new ToggleShowRejected();
186  }
187 
194  private abstract class ObservingChildren<X> extends ChildFactory.Detachable<X> {
195 
201  super();
202  }
203 
208  @Override
209  abstract protected boolean createKeys(List<X> list);
210 
216  @Subscribe
217  abstract void handleReviewStatusChange(ReviewStatusChangeEvent event);
218 
219  @Subscribe
220  abstract void handleDataAdded(ModuleDataEvent event);
221 
222  @Override
223  protected void finalize() throws Throwable {
224  super.finalize();
225  reviewStatusBus.unregister(ObservingChildren.this);
226  }
227 
228  @Override
229  protected void addNotify() {
230  super.addNotify();
231  refresh(true);
232  reviewStatusBus.register(ObservingChildren.this);
233  }
234  }
235 
239  @NbBundle.Messages({"Accounts.RootNode.displayName=Communication Accounts"})
240  final public class AccountsRootNode extends UpdatableCountTypeNode {
241 
242  public AccountsRootNode() {
243  super(Children.create(new AccountTypeFactory(), true),
244  Lookups.singleton(Accounts.this),
245  DISPLAY_NAME,
247  TSK_ACCOUNT);
248 
249  setName(Accounts.NAME);
250  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png"); //NON-NLS
251  }
252 
253  @Override
254  public boolean isLeafTypeNode() {
255  return false;
256  }
257 
258  @Override
259  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
260  return visitor.visit(this);
261  }
262 
263  @Override
264  public String getItemType() {
265  return getClass().getName();
266  }
267 
268  @Override
269  protected long fetchChildCount(SleuthkitCase skCase) throws TskCoreException {
270  String accountTypesInUseQuery
271  = "SELECT COUNT(*) AS count\n"
272  + "FROM (\n"
273  + " SELECT MIN(blackboard_attributes.value_text) AS account_type\n"
274  + " FROM blackboard_artifacts\n"
275  + " LEFT JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id\n"
276  + " WHERE blackboard_artifacts.artifact_type_id = " + TSK_ACCOUNT.getTypeID() + "\n"
277  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.Type.TSK_ACCOUNT_TYPE.getTypeID() + "\n"
278  + " AND blackboard_attributes.value_text IS NOT NULL\n"
279  + getFilterByDataSourceClause() + "\n"
280  + " -- group by artifact_id to ensure only one account type per artifact\n"
281  + " GROUP BY blackboard_artifacts.artifact_id\n"
282  + ") res\n";
283 
284  try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery);
285  ResultSet resultSet = executeQuery.getResultSet()) {
286 
287  if (resultSet.next()) {
288  return resultSet.getLong("count");
289  }
290 
291  } catch (TskCoreException | SQLException ex) {
292  LOGGER.log(Level.SEVERE, "Error querying for count of all account types", ex);
293  }
294 
295  return 0;
296  }
297 
298  }
299 
303  private class AccountTypeResults {
304 
305  private final Map<String, Long> counts = new HashMap<>();
306 
308  update();
309  }
310 
319  Long getCount(String accountType) {
320  return counts.get(accountType);
321  }
322 
328  List<String> getTypes() {
329  List<String> types = new ArrayList<>(counts.keySet());
330  Collections.sort(types);
331  return types;
332  }
333 
337  private void update() {
338  String accountTypesInUseQuery
339  = "SELECT res.account_type, COUNT(*) AS count\n"
340  + "FROM (\n"
341  + " SELECT MIN(blackboard_attributes.value_text) AS account_type\n"
342  + " FROM blackboard_artifacts\n"
343  + " LEFT JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id\n"
344  + " WHERE blackboard_artifacts.artifact_type_id = " + TSK_ACCOUNT.getTypeID() + "\n"
345  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.Type.TSK_ACCOUNT_TYPE.getTypeID() + "\n"
346  + getFilterByDataSourceClause() + "\n"
347  + " -- group by artifact_id to ensure only one account type per artifact\n"
348  + " GROUP BY blackboard_artifacts.artifact_id\n"
349  + ") res\n"
350  + "GROUP BY res.account_type";
351 
352  try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery);
353  ResultSet resultSet = executeQuery.getResultSet()) {
354 
355  counts.clear();
356  while (resultSet.next()) {
357  String accountType = resultSet.getString("account_type");
358  Long count = resultSet.getLong("count");
359  counts.put(accountType, count);
360  }
361  } catch (TskCoreException | SQLException ex) {
362  LOGGER.log(Level.SEVERE, "Error querying for account_types", ex);
363  }
364  }
365  }
366 
370  private class AccountTypeFactory extends ObservingChildren<String> {
371 
372  /*
373  * The pcl is in this class because it has the easiest mechanisms to add
374  * and remove itself during its life cycles.
375  */
376  private final PropertyChangeListener pcl = new PropertyChangeListener() {
377  @Override
378  public void propertyChange(PropertyChangeEvent evt) {
379  String eventType = evt.getPropertyName();
380  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
387  try {
395  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
396  if (null != eventData
397  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
398  accountTypeResults.update();
399  reviewStatusBus.post(eventData);
400  }
401  } catch (NoCurrentCaseException notUsed) {
402  // Case is closed, do nothing.
403  }
404  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
405  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
412  try {
414  accountTypeResults.update();
415  refresh(true);
416  } catch (NoCurrentCaseException notUsed) {
417  // Case is closed, do nothing.
418  }
419  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
420  // case was closed. Remove listeners so that we don't get called with a stale case handle
421  if (evt.getNewValue() == null) {
422  removeNotify();
423  skCase = null;
424  }
425  }
426  }
427  };
428 
429  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
430 
431  @Subscribe
432  @Override
433  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
434  refresh(true);
435  }
436 
437  @Subscribe
438  @Override
439  void handleDataAdded(ModuleDataEvent event) {
440  refresh(true);
441  }
442 
443  @Override
444  protected boolean createKeys(List<String> list) {
445  list.addAll(accountTypeResults.getTypes());
446  return true;
447  }
448 
457  private Node[] getNodeArr(Node node) {
458  reviewStatusBus.register(node);
459  return new Node[]{node};
460  }
461 
462  @Override
463  protected Node[] createNodesForKey(String accountTypeName) {
464 
465  if (Account.Type.CREDIT_CARD.getTypeName().equals(accountTypeName)) {
467  } else {
468 
469  try {
470  Account.Type accountType = skCase.getCommunicationsManager().getAccountType(accountTypeName);
471  if (accountType != null) {
472  return getNodeArr(new DefaultAccountTypeNode(accountType));
473  } else {
474  // This can only happen if a TSK_ACCOUNT artifact was created not using CommunicationManager
475  LOGGER.log(Level.SEVERE, "Unknown account type '" + accountTypeName + "' found - account will not be displayed.\n"
476  + "Account type names must match an entry in the display_name column of the account_types table.\n"
477  + "Accounts should be created using the CommunicationManager API.");
478  }
479  } catch (TskCoreException ex) {
480  LOGGER.log(Level.SEVERE, "Error getting display name for account type. ", ex);
481  }
482 
483  return new Node[]{};
484  }
485  }
486 
487  @Override
488  protected void finalize() throws Throwable {
492  super.finalize();
493  }
494 
495  @Override
496  protected void addNotify() {
500  super.addNotify();
501  refresh(true);
502  }
503 
504  }
505 
506  final private class DefaultAccountFactory extends ObservingChildren<Long> {
507 
508  private final Account.Type accountType;
509 
510  private DefaultAccountFactory(Account.Type accountType) {
511  this.accountType = accountType;
512  }
513 
514  private final PropertyChangeListener pcl = new PropertyChangeListener() {
515  @Override
516  public void propertyChange(PropertyChangeEvent evt) {
517  String eventType = evt.getPropertyName();
518  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
525  try {
533  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
534  if (null != eventData
535  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
536  reviewStatusBus.post(eventData);
537  }
538  } catch (NoCurrentCaseException notUsed) {
539  // Case is closed, do nothing.
540  }
541  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
542  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
549  try {
551  refresh(true);
552 
553  } catch (NoCurrentCaseException notUsed) {
554  // Case is closed, do nothing.
555  }
556  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
557  // case was closed. Remove listeners so that we don't get called with a stale case handle
558  if (evt.getNewValue() == null) {
559  removeNotify();
560  skCase = null;
561  }
562  }
563  }
564  };
565 
566  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
567 
568  @Override
569  protected void addNotify() {
573  }
574 
575  @Override
576  protected void finalize() throws Throwable {
577  super.finalize();
581  }
582 
583  @Override
584  protected boolean createKeys(List<Long> list) {
585  String query
586  = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS
587  + " FROM blackboard_artifacts " //NON-NLS
588  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
589  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
590  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
591  + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS
593  + getRejectedArtifactFilterClause(); //NON-NLS
594  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
595  ResultSet rs = results.getResultSet();) {
596  List<Long> tempList = new ArrayList<>();
597  while (rs.next()) {
598  tempList.add(rs.getLong("artifact_obj_id")); // NON-NLS
599  }
600  list.addAll(tempList);
601  } catch (TskCoreException | SQLException ex) {
602  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
603  }
604 
605  return true;
606  }
607 
608  @Override
609  protected Node[] createNodesForKey(Long t) {
610  try {
611  return new Node[]{new BlackboardArtifactNode(skCase.getBlackboard().getDataArtifactById(t))};
612  } catch (TskCoreException ex) {
613  LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex);
614  return new Node[0];
615  }
616  }
617 
618  @Subscribe
619  @Override
620  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
621  refresh(true);
622  }
623 
624  @Subscribe
625  @Override
626  void handleDataAdded(ModuleDataEvent event) {
627  refresh(true);
628  }
629  }
630 
635  final public class DefaultAccountTypeNode extends DisplayableItemNode {
636 
637  private final Account.Type accountType;
638 
639  private DefaultAccountTypeNode(Account.Type accountType) {
640  super(Children.create(new DefaultAccountFactory(accountType), true), Lookups.singleton(accountType));
641  this.accountType = accountType;
642  String iconPath = getIconFilePath(accountType);
643  this.setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath); //NON-NLS
644  setName(accountType.getTypeName());
645  updateName();
646  }
647 
648  @Override
649  public boolean isLeafTypeNode() {
650  return true;
651  }
652 
653  @Override
654  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
655  return visitor.visit(this);
656  }
657 
658  @Override
659  public String getItemType() {
660  return getClass().getName();
661  }
662 
663  @Subscribe
664  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
665  updateName();
666  }
667 
668  @Subscribe
669  void handleDataAdded(ModuleDataEvent event) {
670  updateName();
671  }
672 
677  public void updateName() {
678  setDisplayName(String.format("%s (%d)", accountType.getDisplayName(), accountTypeResults.getCount(accountType.getTypeName())));
679  }
680  }
681 
685  private enum CreditCardViewMode {
688  }
689 
690  final private class ViewModeFactory extends ObservingChildren<CreditCardViewMode> {
691 
692  private final PropertyChangeListener pcl = new PropertyChangeListener() {
693  @Override
694  public void propertyChange(PropertyChangeEvent evt) {
695  String eventType = evt.getPropertyName();
696  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
703  try {
711  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
712  if (null != eventData
713  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
714  reviewStatusBus.post(eventData);
715  }
716  } catch (NoCurrentCaseException notUsed) {
717  // Case is closed, do nothing.
718  }
719  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
720  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
727  try {
729  refresh(true);
730 
731  } catch (NoCurrentCaseException notUsed) {
732  // Case is closed, do nothing.
733  }
734  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
735  // case was closed. Remove listeners so that we don't get called with a stale case handle
736  if (evt.getNewValue() == null) {
737  removeNotify();
738  skCase = null;
739  }
740  }
741  }
742  };
743 
744  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
745 
746  @Subscribe
747  @Override
748  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
749  refresh(true);
750  }
751 
752  @Subscribe
753  @Override
754  void handleDataAdded(ModuleDataEvent event) {
755  refresh(true);
756  }
757 
758  @Override
759  protected void addNotify() {
763  super.addNotify();
764  }
765 
766  @Override
767  protected void finalize() throws Throwable {
768  super.finalize();
772  super.removeNotify();
773  }
774 
778  @Override
779  protected boolean createKeys(List<CreditCardViewMode> list) {
780  list.addAll(Arrays.asList(CreditCardViewMode.values()));
781 
782  return true;
783  }
784 
785  @Override
786  protected Node[] createNodesForKey(CreditCardViewMode key) {
787  switch (key) {
788  case BY_BIN:
789  return new Node[]{new ByBINNode()};
790  case BY_FILE:
791  return new Node[]{new ByFileNode()};
792  default:
793  return new Node[0];
794  }
795  }
796  }
797 
802 
808  super(Children.create(new ViewModeFactory(), true), Lookups.singleton(Account.Type.CREDIT_CARD.getDisplayName()));
809  setName(Account.Type.CREDIT_CARD.getDisplayName());
810  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS
811  }
812 
817  public void updateName() {
818  setName(String.format("%s (%d)", Account.Type.CREDIT_CARD.getDisplayName(), accountTypeResults.getCount(Account.Type.CREDIT_CARD.getTypeName())));
819  }
820 
821  @Subscribe
822  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
823  updateName();
824  }
825 
826  @Subscribe
827  void handleDataAdded(ModuleDataEvent event) {
828  updateName();
829  }
830 
831  @Override
832  public boolean isLeafTypeNode() {
833  return false;
834  }
835 
836  @Override
837  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
838  return visitor.visit(this);
839  }
840 
841  @Override
842  public String getItemType() {
843  return getClass().getName();
844  }
845  }
846 
847  final private class FileWithCCNFactory extends ObservingChildren<FileWithCCN> {
848 
849  private final PropertyChangeListener pcl = new PropertyChangeListener() {
850  @Override
851  public void propertyChange(PropertyChangeEvent evt) {
852  String eventType = evt.getPropertyName();
853  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
860  try {
868  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
869  if (null != eventData
870  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
871  reviewStatusBus.post(eventData);
872  }
873  } catch (NoCurrentCaseException notUsed) {
874  // Case is closed, do nothing.
875  }
876  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
877  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
884  try {
886  refresh(true);
887 
888  } catch (NoCurrentCaseException notUsed) {
889  // Case is closed, do nothing.
890  }
891  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
892  // case was closed. Remove listeners so that we don't get called with a stale case handle
893  if (evt.getNewValue() == null) {
894  removeNotify();
895  skCase = null;
896  }
897  }
898  }
899  };
900 
901  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
902 
903  @Override
904  protected void addNotify() {
908  super.addNotify();
909  }
910 
911  @Override
912  protected void finalize() throws Throwable {
913  super.finalize();
917  }
918 
919  @Subscribe
920  @Override
921  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
922  refresh(true);
923  }
924 
925  @Subscribe
926  @Override
927  void handleDataAdded(ModuleDataEvent event) {
928  refresh(true);
929  }
930 
931  @Override
932  protected boolean createKeys(List<FileWithCCN> list) {
933  String query
934  = "SELECT blackboard_artifacts.obj_id," //NON-NLS
935  + " solr_attribute.value_text AS solr_document_id, "; //NON-NLS
936  if (skCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
937  query += " string_agg(blackboard_artifacts.artifact_id::character varying, ',') AS artifact_IDs, " //NON-NLS
938  + " string_agg(blackboard_artifacts.review_status_id::character varying, ',') AS review_status_ids, ";
939  } else {
940  query += " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS
941  + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids, ";
942  }
943  query += " COUNT( blackboard_artifacts.artifact_id) AS hits " //NON-NLS
944  + " FROM blackboard_artifacts " //NON-NLS
945  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
946  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
947  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
948  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
949  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
950  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
953  + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS
954  + " ORDER BY hits DESC "; //NON-NLS
955  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
956  ResultSet resultSet = results.getResultSet();) {
957  while (resultSet.next()) {
958  long file_id = resultSet.getLong("obj_id");
959  AbstractFile abstractFileById = skCase.getAbstractFileById(file_id);
960  if(abstractFileById != null) {
961  list.add(new FileWithCCN(
962  abstractFileById,
963  file_id, //NON-NLS
964  resultSet.getString("solr_document_id"), //NON-NLS
965  unGroupConcat(resultSet.getString("artifact_IDs"), Long::valueOf), //NON-NLS
966  resultSet.getLong("hits"), //NON-NLS
967  new HashSet<>(unGroupConcat(resultSet.getString("review_status_ids"), reviewStatusID -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(reviewStatusID)))))); //NON-NLS
968  }
969  }
970  } catch (TskCoreException | SQLException ex) {
971  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
972 
973  }
974  return true;
975  }
976 
977  @Override
978  protected Node[] createNodesForKey(FileWithCCN key) {
979  //add all account artifacts for the file and the file itself to the lookup
980  try {
981  List<Object> lookupContents = new ArrayList<>();
982  for (long artId : key.artifactIDs) {
983  lookupContents.add(skCase.getBlackboardArtifact(artId));
984  }
985  AbstractFile abstractFileById = key.getFile();
986  lookupContents.add(abstractFileById);
987  return new Node[]{new FileWithCCNNode(key, abstractFileById, lookupContents.toArray())};
988  } catch (TskCoreException ex) {
989  LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS
990  return new Node[0];
991  }
992  }
993  }
994 
999  final public class ByFileNode extends DisplayableItemNode {
1000 
1004  private ByFileNode() {
1005  super(Children.create(new FileWithCCNFactory(), true), Lookups.singleton("By File"));
1006  setName("By File"); //NON-NLS
1007  updateDisplayName();
1008  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS
1009  reviewStatusBus.register(this);
1010  }
1011 
1012  @NbBundle.Messages({
1013  "# {0} - number of children",
1014  "Accounts.ByFileNode.displayName=By File ({0})"})
1015  private void updateDisplayName() {
1016  String query
1017  = "SELECT count(*) FROM ( SELECT count(*) AS documents "
1018  + " FROM blackboard_artifacts " //NON-NLS
1019  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
1020  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
1021  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
1022  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
1023  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
1024  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1027  + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo";
1028  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1029  ResultSet resultSet = results.getResultSet();) {
1030  while (resultSet.next()) {
1031  if (skCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
1032  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count")));
1033  } else {
1034  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count(*)")));
1035  }
1036  }
1037  } catch (TskCoreException | SQLException ex) {
1038  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
1039 
1040  }
1041  }
1042 
1043  @Override
1044  public boolean isLeafTypeNode() {
1045  return true;
1046  }
1047 
1048  @Override
1049  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1050  return visitor.visit(this);
1051  }
1052 
1053  @Override
1054  public String getItemType() {
1055  return getClass().getName();
1056  }
1057 
1058  @Subscribe
1059  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1060  updateDisplayName();
1061  }
1062 
1063  @Subscribe
1064  void handleDataAdded(ModuleDataEvent event) {
1065  updateDisplayName();
1066  }
1067  }
1068 
1069  final private class BINFactory extends ObservingChildren<BinResult> {
1070 
1071  private final PropertyChangeListener pcl = new PropertyChangeListener() {
1072  @Override
1073  public void propertyChange(PropertyChangeEvent evt) {
1074  String eventType = evt.getPropertyName();
1075  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
1082  try {
1090  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
1091  if (null != eventData
1092  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
1093  reviewStatusBus.post(eventData);
1094  }
1095  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
1096  // Case is closed, do nothing.
1097  }
1098  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
1099  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
1106  try {
1108 
1109  refresh(true);
1110  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
1111  // Case is closed, do nothing.
1112  }
1113  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())
1114  && (evt.getNewValue() == null)) {
1115  // case was closed. Remove listeners so that we don't get called with a stale case handle
1116  removeNotify();
1117  skCase = null;
1118  }
1119  }
1120  };
1121 
1122  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
1123 
1124  @Override
1125  protected void addNotify() {
1129  super.addNotify();
1130  }
1131 
1132  @Override
1133  protected void finalize() throws Throwable {
1134  super.finalize();
1138  }
1139 
1140  @Subscribe
1141  @Override
1142  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1143  refresh(true);
1144  }
1145 
1146  @Subscribe
1147  @Override
1148  void handleDataAdded(ModuleDataEvent event) {
1149  refresh(true);
1150  }
1151 
1152  @Override
1153  protected boolean createKeys(List<BinResult> list) {
1154 
1155  RangeMap<Integer, BinResult> binRanges = TreeRangeMap.create();
1156 
1157  String query
1158  = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS
1159  + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS
1160  + " FROM blackboard_artifacts " //NON-NLS
1161  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
1162  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1163  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1166  + " GROUP BY BIN " //NON-NLS
1167  + " ORDER BY BIN "; //NON-NLS
1168  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1169  ResultSet resultSet = results.getResultSet();) {
1170  //sort all te individual bins in to the ranges
1171  while (resultSet.next()) {
1172  final Integer bin = Integer.valueOf(resultSet.getString("BIN"));
1173  long count = resultSet.getLong("count");
1174 
1175  BINRange binRange = (BINRange) CreditCards.getBINInfo(bin);
1176  BinResult previousResult = binRanges.get(bin);
1177 
1178  if (previousResult != null) {
1179  binRanges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd()));
1180  count += previousResult.getCount();
1181  }
1182 
1183  if (binRange == null) {
1184  binRanges.put(Range.closed(bin, bin), new BinResult(count, bin, bin));
1185  } else {
1186  binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange));
1187  }
1188  }
1189  binRanges.asMapOfRanges().values().forEach(list::add);
1190  } catch (TskCoreException | SQLException ex) {
1191  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1192  }
1193 
1194  return true;
1195  }
1196 
1197  @Override
1198  protected Node[] createNodesForKey(BinResult key) {
1199  return new Node[]{new BINNode(key)};
1200  }
1201  }
1202 
1207  final public class ByBINNode extends DisplayableItemNode {
1208 
1212  @NbBundle.Messages("Accounts.ByBINNode.name=By BIN")
1213  private ByBINNode() {
1214  super(Children.create(new BINFactory(), true), Lookups.singleton(Bundle.Accounts_ByBINNode_name()));
1215  setName(Bundle.Accounts_ByBINNode_name()); //NON-NLS
1216  updateDisplayName();
1217  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1218  reviewStatusBus.register(this);
1219  }
1220 
1221  @NbBundle.Messages({
1222  "# {0} - number of children",
1223  "Accounts.ByBINNode.displayName=By BIN ({0})"})
1224  private void updateDisplayName() {
1225  String query
1226  = "SELECT count(distinct SUBSTR(blackboard_attributes.value_text,1,8)) AS BINs " //NON-NLS
1227  + " FROM blackboard_artifacts " //NON-NLS
1228  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
1229  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1230  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1232  + getRejectedArtifactFilterClause(); //NON-NLS
1233  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1234  ResultSet resultSet = results.getResultSet();) {
1235  while (resultSet.next()) {
1236  setDisplayName(Bundle.Accounts_ByBINNode_displayName(resultSet.getLong("BINs")));
1237  }
1238  } catch (TskCoreException | SQLException ex) {
1239  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1240  }
1241  }
1242 
1243  @Override
1244  public boolean isLeafTypeNode() {
1245  return false;
1246  }
1247 
1248  @Override
1249  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1250  return visitor.visit(this);
1251  }
1252 
1253  @Override
1254  public String getItemType() {
1255  return getClass().getName();
1256  }
1257 
1258  @Subscribe
1259  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1260  updateDisplayName();
1261  }
1262 
1263  @Subscribe
1264  void handleDataAdded(ModuleDataEvent event) {
1265  updateDisplayName();
1266  }
1267  }
1268 
1273  @Immutable
1274  final private static class FileWithCCN {
1275 
1276  @Override
1277  public int hashCode() {
1278  int hash = 5;
1279  hash = 79 * hash + (int) (this.objID ^ (this.objID >>> 32));
1280  hash = 79 * hash + Objects.hashCode(this.keywordSearchDocID);
1281  hash = 79 * hash + Objects.hashCode(this.artifactIDs);
1282  hash = 79 * hash + (int) (this.hits ^ (this.hits >>> 32));
1283  hash = 79 * hash + Objects.hashCode(this.statuses);
1284  return hash;
1285  }
1286 
1287  @Override
1288  public boolean equals(Object obj) {
1289  if (this == obj) {
1290  return true;
1291  }
1292  if (obj == null) {
1293  return false;
1294  }
1295  if (getClass() != obj.getClass()) {
1296  return false;
1297  }
1298  final FileWithCCN other = (FileWithCCN) obj;
1299  if (this.objID != other.objID) {
1300  return false;
1301  }
1302  if (this.hits != other.hits) {
1303  return false;
1304  }
1305  if (!Objects.equals(this.keywordSearchDocID, other.keywordSearchDocID)) {
1306  return false;
1307  }
1308  if (!Objects.equals(this.artifactIDs, other.artifactIDs)) {
1309  return false;
1310  }
1311  if (!Objects.equals(this.statuses, other.statuses)) {
1312  return false;
1313  }
1314  return true;
1315  }
1316 
1317  private final long objID;
1318  private final String keywordSearchDocID;
1319  private final List<Long> artifactIDs;
1320  private final long hits;
1321  private final Set<BlackboardArtifact.ReviewStatus> statuses;
1322  private final AbstractFile file;
1323 
1324  private FileWithCCN(AbstractFile file, long objID, String solrDocID, List<Long> artifactIDs, long hits, Set<BlackboardArtifact.ReviewStatus> statuses) {
1325  this.objID = objID;
1326  this.keywordSearchDocID = solrDocID;
1327  this.artifactIDs = artifactIDs;
1328  this.hits = hits;
1329  this.statuses = statuses;
1330  this.file = file;
1331  }
1332 
1338  public long getObjID() {
1339  return objID;
1340  }
1341 
1348  public String getkeywordSearchDocID() {
1349  return keywordSearchDocID;
1350  }
1351 
1357  public List<Long> getArtifactIDs() {
1358  return Collections.unmodifiableList(artifactIDs);
1359  }
1360 
1366  public long getHits() {
1367  return hits;
1368  }
1369 
1375  public Set<BlackboardArtifact.ReviewStatus> getStatuses() {
1376  return Collections.unmodifiableSet(statuses);
1377  }
1378 
1379  AbstractFile getFile() {
1380  return file;
1381  }
1382  }
1383 
1400  static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
1401  return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
1402  : Stream.of(groupConcat.split(",")) //NON-NLS
1403  .map(mapper::apply)
1404  .collect(Collectors.toList());
1405  }
1406 
1410  final public class FileWithCCNNode extends DisplayableItemNode {
1411 
1412  private final FileWithCCN fileKey;
1413  private final String fileName;
1414 
1424  @NbBundle.Messages({
1425  "# {0} - raw file name",
1426  "# {1} - solr chunk id",
1427  "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"})
1428  private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) {
1429  super(Children.LEAF, Lookups.fixed(lookupContents));
1430  this.fileKey = key;
1431  this.fileName = (key.getkeywordSearchDocID() == null)
1432  ? content.getName()
1433  : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getkeywordSearchDocID(), "_")); //NON-NLS
1434  setName(fileName + key.getObjID());
1435  setDisplayName(fileName);
1436  }
1437 
1438  @Override
1439  public boolean isLeafTypeNode() {
1440  return true;
1441  }
1442 
1443  @Override
1444  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1445  return visitor.visit(this);
1446  }
1447 
1448  @Override
1449  public String getItemType() {
1450  return getClass().getName();
1451  }
1452 
1453  @Override
1454  @NbBundle.Messages({
1455  "Accounts.FileWithCCNNode.nameProperty.displayName=File",
1456  "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts",
1457  "Accounts.FileWithCCNNode.statusProperty.displayName=Status",
1458  "Accounts.FileWithCCNNode.noDescription=no description"})
1459  protected Sheet createSheet() {
1460  Sheet sheet = super.createSheet();
1461  Sheet.Set propSet = sheet.get(Sheet.PROPERTIES);
1462  if (propSet == null) {
1463  propSet = Sheet.createPropertiesSet();
1464  sheet.put(propSet);
1465  }
1466 
1467  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1468  Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1469  Bundle.Accounts_FileWithCCNNode_noDescription(),
1470  fileName));
1471  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1472  Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1473  Bundle.Accounts_FileWithCCNNode_noDescription(),
1474  fileKey.getHits()));
1475  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1476  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1477  Bundle.Accounts_FileWithCCNNode_noDescription(),
1478  fileKey.getStatuses().stream()
1479  .map(BlackboardArtifact.ReviewStatus::getDisplayName)
1480  .collect(Collectors.joining(", ")))); //NON-NLS
1481 
1482  return sheet;
1483  }
1484 
1485  @Override
1486  public Action[] getActions(boolean context) {
1487  Action[] actions = super.getActions(context);
1488  ArrayList<Action> arrayList = new ArrayList<>();
1489  try {
1490  arrayList.addAll(DataModelActionsFactory.getActions(Accounts.this.skCase.getContentById(fileKey.getObjID()), false));
1491  } catch (TskCoreException ex) {
1492  LOGGER.log(Level.SEVERE, "Error gettung content by id", ex);
1493  }
1494 
1495  arrayList.add(approveActionInstance);
1496  arrayList.add(rejectActionInstance);
1497  arrayList.add(null);
1498  arrayList.addAll(Arrays.asList(actions));
1499  return arrayList.toArray(new Action[arrayList.size()]);
1500  }
1501  }
1502 
1503  final private class CreditCardNumberFactory extends ObservingChildren<DataArtifact> {
1504 
1505  private final BinResult bin;
1506 
1508  this.bin = bin;
1509  }
1510 
1511  @Subscribe
1512  @Override
1513  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1514  refresh(true);
1515  }
1516 
1517  @Subscribe
1518  @Override
1519  void handleDataAdded(ModuleDataEvent event) {
1520  refresh(true);
1521  }
1522 
1523  @Override
1524  protected boolean createKeys(List<DataArtifact> list) {
1525 
1526  String query
1527  = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS
1528  + " FROM blackboard_artifacts " //NON-NLS
1529  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1530  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1531  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1532  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1535  + " ORDER BY blackboard_attributes.value_text"; //NON-NLS
1536  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1537  ResultSet rs = results.getResultSet();) {
1538  while (rs.next()) {
1539  list.add(skCase.getBlackboard().getDataArtifactById(rs.getLong("artifact_obj_id"))); //NON-NLS
1540  }
1541  } catch (TskCoreException | SQLException ex) {
1542  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1543 
1544  }
1545  return true;
1546  }
1547 
1548  @Override
1549  protected Node[] createNodesForKey(DataArtifact artifact) {
1550  return new Node[]{new AccountArtifactNode(artifact)};
1551  }
1552  }
1553 
1554  private String getBinRangeString(BinResult bin) {
1555  if (bin.getBINStart() == bin.getBINEnd()) {
1556  return Integer.toString(bin.getBINStart());
1557  } else {
1558  return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + "");
1559  }
1560  }
1561 
1562  final public class BINNode extends DisplayableItemNode {
1563 
1567  private final BinResult bin;
1568 
1569  private BINNode(BinResult bin) {
1570  super(Children.create(new CreditCardNumberFactory(bin), true), Lookups.singleton(getBinRangeString(bin)));
1571  this.bin = bin;
1572  setName(getBinRangeString(bin));
1573  updateDisplayName();
1574  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1575  reviewStatusBus.register(this);
1576  }
1577 
1578  @Subscribe
1579  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1580  updateDisplayName();
1581  updateSheet();
1582  }
1583 
1584  @Subscribe
1585  void handleDataAdded(ModuleDataEvent event) {
1586  updateDisplayName();
1587  }
1588 
1589  private void updateDisplayName() {
1590  String query
1591  = "SELECT count(blackboard_artifacts.artifact_id ) AS count" //NON-NLS
1592  + " FROM blackboard_artifacts " //NON-NLS
1593  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1594  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1595  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1596  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1599  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1600  ResultSet resultSet = results.getResultSet();) {
1601  while (resultSet.next()) {
1602  setDisplayName(getBinRangeString(bin) + " (" + resultSet.getLong("count") + ")"); //NON-NLS
1603  }
1604  } catch (TskCoreException | SQLException ex) {
1605  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1606 
1607  }
1608 
1609  }
1610 
1611  @Override
1612  public boolean isLeafTypeNode() {
1613  return true;
1614  }
1615 
1616  @Override
1617  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1618  return visitor.visit(this);
1619  }
1620 
1621  @Override
1622  public String getItemType() {
1623  return getClass().getName();
1624  }
1625 
1626  private Sheet.Set getPropertySet(Sheet sheet) {
1627  Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
1628  if (sheetSet == null) {
1629  sheetSet = Sheet.createPropertiesSet();
1630  sheet.put(sheetSet);
1631  }
1632  return sheetSet;
1633  }
1634 
1635  @Override
1636  @NbBundle.Messages({
1637  "Accounts.BINNode.binProperty.displayName=Bank Identifier Number",
1638  "Accounts.BINNode.accountsProperty.displayName=Accounts",
1639  "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type",
1640  "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme",
1641  "Accounts.BINNode.brandProperty.displayName=Brand",
1642  "Accounts.BINNode.bankProperty.displayName=Bank",
1643  "Accounts.BINNode.bankCityProperty.displayName=Bank City",
1644  "Accounts.BINNode.bankCountryProperty.displayName=Bank Country",
1645  "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #",
1646  "Accounts.BINNode.bankURLProperty.displayName=Bank URL",
1647  "Accounts.BINNode.noDescription=no description"})
1648  protected Sheet createSheet() {
1649  Sheet sheet = super.createSheet();
1650  Sheet.Set properties = getPropertySet(sheet);
1651 
1652  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(),
1653  Bundle.Accounts_BINNode_binProperty_displayName(),
1654  Bundle.Accounts_BINNode_noDescription(),
1655  getBinRangeString(bin)));
1656  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(),
1657  Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1658  bin.getCount()));
1659 
1660  //add optional properties if they are available
1661  if (bin.hasDetails()) {
1662  bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(),
1663  Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1664  cardType)));
1665  bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(),
1666  Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1667  scheme)));
1668  bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(),
1669  Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1670  brand)));
1671  bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(),
1672  Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1673  bankName)));
1674  bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(),
1675  Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1676  bankCity)));
1677  bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(),
1678  Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1679  country)));
1680  bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(),
1681  Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1682  phoneNumber)));
1683  bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(),
1684  Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1685  url)));
1686  }
1687  return sheet;
1688  }
1689 
1690  private void updateSheet() {
1691  SwingUtilities.invokeLater(() -> {
1692  this.setSheet(createSheet());
1693  });
1694  }
1695 
1696  }
1697 
1702  @Immutable
1703  final static private class BinResult implements CreditCards.BankIdentificationNumber {
1704 
1705  @Override
1706  public int hashCode() {
1707  int hash = 3;
1708  hash = 97 * hash + this.binEnd;
1709  hash = 97 * hash + this.binStart;
1710  return hash;
1711  }
1712 
1713  @Override
1714  public boolean equals(Object obj) {
1715  if (this == obj) {
1716  return true;
1717  }
1718  if (obj == null) {
1719  return false;
1720  }
1721  if (getClass() != obj.getClass()) {
1722  return false;
1723  }
1724  final BinResult other = (BinResult) obj;
1725  if (this.binEnd != other.binEnd) {
1726  return false;
1727  }
1728  if (this.binStart != other.binStart) {
1729  return false;
1730  }
1731  return true;
1732  }
1733 
1737  private final long count;
1738 
1739  private final BINRange binRange;
1740  private final int binEnd;
1741  private final int binStart;
1742 
1743  private BinResult(long count, @Nonnull BINRange binRange) {
1744  this.count = count;
1745  this.binRange = binRange;
1746  binStart = binRange.getBINstart();
1747  binEnd = binRange.getBINend();
1748  }
1749 
1750  private BinResult(long count, int start, int end) {
1751  this.count = count;
1752  this.binRange = null;
1753  binStart = start;
1754  binEnd = end;
1755  }
1756 
1757  int getBINStart() {
1758  return binStart;
1759  }
1760 
1761  int getBINEnd() {
1762  return binEnd;
1763  }
1764 
1765  long getCount() {
1766  return count;
1767  }
1768 
1769  boolean hasDetails() {
1770  return binRange != null;
1771  }
1772 
1773  @Override
1774  public Optional<Integer> getNumberLength() {
1775  return binRange.getNumberLength();
1776  }
1777 
1778  @Override
1779  public Optional<String> getBankCity() {
1780  return binRange.getBankCity();
1781  }
1782 
1783  @Override
1784  public Optional<String> getBankName() {
1785  return binRange.getBankName();
1786  }
1787 
1788  @Override
1789  public Optional<String> getBankPhoneNumber() {
1790  return binRange.getBankPhoneNumber();
1791  }
1792 
1793  @Override
1794  public Optional<String> getBankURL() {
1795  return binRange.getBankURL();
1796  }
1797 
1798  @Override
1799  public Optional<String> getBrand() {
1800  return binRange.getBrand();
1801  }
1802 
1803  @Override
1804  public Optional<String> getCardType() {
1805  return binRange.getCardType();
1806  }
1807 
1808  @Override
1809  public Optional<String> getCountry() {
1810  return binRange.getCountry();
1811  }
1812 
1813  @Override
1814  public Optional<String> getScheme() {
1815  return binRange.getScheme();
1816  }
1817  }
1818 
1819  final private class AccountArtifactNode extends BlackboardArtifactNode {
1820 
1821  private final BlackboardArtifact artifact;
1822 
1823  private AccountArtifactNode(BlackboardArtifact artifact) {
1824  super(artifact, "org/sleuthkit/autopsy/images/credit-card.png"); //NON-NLS
1825  this.artifact = artifact;
1826  setName(Long.toString(this.artifact.getArtifactID()));
1827 
1828  reviewStatusBus.register(this);
1829  }
1830 
1831  @Override
1832  public Action[] getActions(boolean context) {
1833  List<Action> actionsList = new ArrayList<>();
1834  actionsList.addAll(Arrays.asList(super.getActions(context)));
1835 
1836  actionsList.add(approveActionInstance);
1837  actionsList.add(rejectActionInstance);
1838 
1839  return actionsList.toArray(new Action[actionsList.size()]);
1840  }
1841 
1842  @Override
1843  protected Sheet createSheet() {
1844  Sheet sheet = super.createSheet();
1845  Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
1846  if (properties == null) {
1847  properties = Sheet.createPropertiesSet();
1848  sheet.put(properties);
1849  }
1850  properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1851  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1852  Bundle.Accounts_FileWithCCNNode_noDescription(),
1853  artifact.getReviewStatus().getDisplayName()));
1854 
1855  return sheet;
1856  }
1857 
1858  @Subscribe
1859  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1860 
1861  // Update the node if event includes this artifact
1862  event.artifacts.stream().filter((art) -> (art.getArtifactID() == this.artifact.getArtifactID())).map((_item) -> {
1863  return _item;
1864  }).forEachOrdered((_item) -> {
1865  updateSheet();
1866  });
1867  }
1868 
1869  private void updateSheet() {
1870  SwingUtilities.invokeLater(() -> {
1871  this.setSheet(createSheet());
1872  });
1873  }
1874 
1875  }
1876 
1877  @Deprecated
1878  private final class ToggleShowRejected extends AbstractAction {
1879 
1880  @NbBundle.Messages("ToggleShowRejected.name=Show Rejected Results")
1881  ToggleShowRejected() {
1882  super(Bundle.ToggleShowRejected_name());
1883  }
1884 
1885  @Override
1886  public void actionPerformed(ActionEvent e) {
1887  showRejected = !showRejected;
1888  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1889  }
1890  }
1891 
1897  public void setShowRejected(boolean showRejected) {
1898  this.showRejected = showRejected;
1899  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1900  }
1901 
1902  private abstract class ReviewStatusAction extends AbstractAction {
1903 
1904  private final BlackboardArtifact.ReviewStatus newStatus;
1905 
1906  private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) {
1907  super(displayName);
1908  this.newStatus = newStatus;
1909 
1910  }
1911 
1912  @Override
1913  public void actionPerformed(ActionEvent e) {
1914 
1915  /*
1916  * get paths for selected nodes to reselect after applying review
1917  * status change
1918  */
1919  List<String[]> selectedPaths = Utilities.actionsGlobalContext().lookupAll(Node.class).stream()
1920  .map(node -> {
1921  String[] createPath;
1922  /*
1923  * If the we are rejecting and not showing rejected
1924  * results, then the selected node, won't exist any
1925  * more, so we select the previous one in stead.
1926  */
1927  if (newStatus == BlackboardArtifact.ReviewStatus.REJECTED && showRejected == false) {
1928  List<Node> siblings = Arrays.asList(node.getParentNode().getChildren().getNodes());
1929  if (siblings.size() > 1) {
1930  int indexOf = siblings.indexOf(node);
1931  //there is no previous for the first node, so instead we select the next one
1932  Node sibling = indexOf > 0
1933  ? siblings.get(indexOf - 1)
1934  : siblings.get(Integer.max(indexOf + 1, siblings.size() - 1));
1935  createPath = NodeOp.createPath(sibling, null);
1936  } else {
1937  /*
1938  * if there are no other siblings to select,
1939  * just return null, but note we need to filter
1940  * this out of stream below
1941  */
1942  return null;
1943  }
1944  } else {
1945  createPath = NodeOp.createPath(node, null);
1946  }
1947  //for the reselect to work we need to strip off the first part of the path.
1948  return Arrays.copyOfRange(createPath, 1, createPath.length);
1949  })
1950  .filter(Objects::nonNull)
1951  .collect(Collectors.toList());
1952 
1953  //change status of selected artifacts
1954  final Collection<? extends BlackboardArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
1955  artifacts.forEach(artifact -> {
1956  try {
1957  artifact.setReviewStatus(newStatus);
1958  } catch (TskCoreException ex) {
1959  LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS
1960  }
1961  });
1962  //post event
1963  reviewStatusBus.post(new ReviewStatusChangeEvent(artifacts, newStatus));
1964 
1965  final DataResultTopComponent directoryListing = DirectoryTreeTopComponent.findInstance().getDirectoryListing();
1966  final Node rootNode = directoryListing.getRootNode();
1967 
1968  //convert paths back to nodes
1969  List<Node> toArray = new ArrayList<>();
1970  selectedPaths.forEach(path -> {
1971  try {
1972  toArray.add(NodeOp.findPath(rootNode, path));
1973  } catch (NodeNotFoundException ex) { //NOPMD empty catch clause
1974  //just ingnore paths taht don't exist. this is expected since we are rejecting
1975  }
1976  });
1977  //select nodes
1978  directoryListing.setSelectedNodes(toArray.toArray(new Node[toArray.size()]));
1979  }
1980  }
1981 
1982  final private class ApproveAccounts extends ReviewStatusAction {
1983 
1984  @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"})
1985  private ApproveAccounts() {
1986  super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED);
1987  }
1988  }
1989 
1990  final private class RejectAccounts extends ReviewStatusAction {
1991 
1992  @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"})
1993  private RejectAccounts() {
1994  super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED);
1995  }
1996  }
1997 
1998  static private class ReviewStatusChangeEvent {
1999 
2000  Collection<? extends BlackboardArtifact> artifacts;
2001  BlackboardArtifact.ReviewStatus newReviewStatus;
2002 
2003  ReviewStatusChangeEvent(Collection<? extends BlackboardArtifact> artifacts, BlackboardArtifact.ReviewStatus newReviewStatus) {
2004  this.artifacts = artifacts;
2005  this.newReviewStatus = newReviewStatus;
2006  }
2007  }
2008 
2014  public static String getIconFilePath(Account.Type type) {
2015 
2016  if (type.equals(Account.Type.CREDIT_CARD)) {
2017  return ICON_BASE_PATH + "credit-card.png";
2018  } else if (type.equals(Account.Type.DEVICE)) {
2019  return ICON_BASE_PATH + "image.png";
2020  } else if (type.equals(Account.Type.EMAIL)) {
2021  return ICON_BASE_PATH + "email.png";
2022  } else if (type.equals(Account.Type.FACEBOOK)) {
2023  return ICON_BASE_PATH + "facebook.png";
2024  } else if (type.equals(Account.Type.INSTAGRAM)) {
2025  return ICON_BASE_PATH + "instagram.png";
2026  } else if (type.equals(Account.Type.MESSAGING_APP)) {
2027  return ICON_BASE_PATH + "messaging.png";
2028  } else if (type.equals(Account.Type.PHONE)) {
2029  return ICON_BASE_PATH + "phone.png";
2030  } else if (type.equals(Account.Type.TWITTER)) {
2031  return ICON_BASE_PATH + "twitter.png";
2032  } else if (type.equals(Account.Type.WEBSITE)) {
2033  return ICON_BASE_PATH + "web-file.png";
2034  } else if (type.equals(Account.Type.WHATSAPP)) {
2035  return ICON_BASE_PATH + "WhatsApp.png";
2036  } else {
2037  //there could be a default icon instead...
2038  return ICON_BASE_PATH + "face.png";
2039 // throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName());
2040  }
2041  }
2042 }
static final Set< IngestManager.IngestModuleEvent > INGEST_MODULE_EVENTS_OF_INTEREST
Definition: Accounts.java:100
boolean createKeys(List< CreditCardViewMode > list)
Definition: Accounts.java:779
BlackboardArtifact.Type getBlackboardArtifactType()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static synchronized IngestManager getInstance()
Set< BlackboardArtifact.ReviewStatus > getStatuses()
Definition: Accounts.java:1375
static List< Action > getActions(File file, boolean isArtifactSource)
static synchronized BankIdentificationNumber getBINInfo(int bin)
static String getIconFilePath(Account.Type type)
Definition: Accounts.java:2014
void removeIngestJobEventListener(final PropertyChangeListener listener)
final Set< BlackboardArtifact.ReviewStatus > statuses
Definition: Accounts.java:1321
void addIngestJobEventListener(final PropertyChangeListener listener)
BinResult(long count,@Nonnull BINRange binRange)
Definition: Accounts.java:1743
FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents)
Definition: Accounts.java:1428
FileWithCCN(AbstractFile file, long objID, String solrDocID, List< Long > artifactIDs, long hits, Set< BlackboardArtifact.ReviewStatus > statuses)
Definition: Accounts.java:1324
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
Accounts(SleuthkitCase skCase, long objId)
Definition: Accounts.java:137
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:711
static final Set< IngestManager.IngestJobEvent > INGEST_JOB_EVENTS_OF_INTEREST
Definition: Accounts.java:99
ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus)
Definition: Accounts.java:1906
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:756

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