Autopsy  4.21.0
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 final long filteringDSObjId; // 0 if not filtering/grouping by data source
107 
108  private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus");
109 
110  /*
111  * Should rejected accounts be shown in the accounts section of the tree.
112  */
113  private boolean showRejected = false; //NOPMD redundant initializer
114 
117 
118  // tracks the number of each account type found
120 
126  public Accounts() {
127  this(0);
128  }
129 
136  public Accounts(long objId) {
137  this.filteringDSObjId = objId;
138 
139  this.rejectActionInstance = new RejectAccounts();
140  this.approveActionInstance = new ApproveAccounts();
141  this.accountTypeResults = new AccountTypeResults();
142  }
143 
144  @Override
145  public <T> T accept(AutopsyItemVisitor<T> visitor) {
146  return visitor.visit(this);
147  }
148 
157  return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS
158  }
159 
166  private String getFilterByDataSourceClause() {
167  if (filteringDSObjId > 0) {
168  return " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId + " ";
169  }
170 
171  return " ";
172  }
173 
181  @Deprecated
182  public Action newToggleShowRejectedAction() {
183  return new ToggleShowRejected();
184  }
185 
192  private abstract class ObservingChildren<X> extends ChildFactory.Detachable<X> {
193 
199  super();
200  }
201 
206  @Override
207  abstract protected boolean createKeys(List<X> list);
208 
214  @Subscribe
215  abstract void handleReviewStatusChange(ReviewStatusChangeEvent event);
216 
217  @Subscribe
218  abstract void handleDataAdded(ModuleDataEvent event);
219 
220  @Override
221  protected void finalize() throws Throwable {
222  super.finalize();
223  reviewStatusBus.unregister(ObservingChildren.this);
224  }
225 
226  @Override
227  protected void addNotify() {
228  super.addNotify();
229  refresh(true);
230  reviewStatusBus.register(ObservingChildren.this);
231  }
232  }
233 
237  @NbBundle.Messages({"Accounts.RootNode.displayName=Communication Accounts"})
238  final public class AccountsRootNode extends UpdatableCountTypeNode {
239 
240  public AccountsRootNode() {
241  super(Children.create(new AccountTypeFactory(), true),
242  Lookups.singleton(Accounts.this),
243  DISPLAY_NAME,
245  TSK_ACCOUNT);
246 
247  setName(Accounts.NAME);
248  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png"); //NON-NLS
249  }
250 
251  @Override
252  public boolean isLeafTypeNode() {
253  return false;
254  }
255 
256  @Override
257  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
258  return visitor.visit(this);
259  }
260 
261  @Override
262  public String getItemType() {
263  return getClass().getName();
264  }
265 
266  @Override
267  protected long fetchChildCount(SleuthkitCase skCase) throws TskCoreException {
268  String accountTypesInUseQuery
269  = "SELECT COUNT(*) AS count\n"
270  + "FROM (\n"
271  + " SELECT MIN(blackboard_attributes.value_text) AS account_type\n"
272  + " FROM blackboard_artifacts\n"
273  + " LEFT JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id\n"
274  + " WHERE blackboard_artifacts.artifact_type_id = " + TSK_ACCOUNT.getTypeID() + "\n"
275  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.Type.TSK_ACCOUNT_TYPE.getTypeID() + "\n"
276  + " AND blackboard_attributes.value_text IS NOT NULL\n"
277  + getFilterByDataSourceClause() + "\n"
278  + " -- group by artifact_id to ensure only one account type per artifact\n"
279  + " GROUP BY blackboard_artifacts.artifact_id\n"
280  + ") res\n";
281 
282  try (SleuthkitCase.CaseDbQuery executeQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(accountTypesInUseQuery);
283  ResultSet resultSet = executeQuery.getResultSet()) {
284 
285  if (resultSet.next()) {
286  return resultSet.getLong("count");
287  }
288 
289  } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
290  LOGGER.log(Level.SEVERE, "Error querying for count of all account types", ex);
291  }
292 
293  return 0;
294  }
295 
296  }
297 
301  private class AccountTypeResults {
302 
303  private final Map<String, Long> counts = new HashMap<>();
304 
306  update();
307  }
308 
317  Long getCount(String accountType) {
318  return counts.get(accountType);
319  }
320 
326  List<String> getTypes() {
327  List<String> types = new ArrayList<>(counts.keySet());
328  Collections.sort(types);
329  return types;
330  }
331 
335  private void update() {
336  String accountTypesInUseQuery
337  = "SELECT res.account_type, COUNT(*) AS count\n"
338  + "FROM (\n"
339  + " SELECT MIN(blackboard_attributes.value_text) AS account_type\n"
340  + " FROM blackboard_artifacts\n"
341  + " LEFT JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id\n"
342  + " WHERE blackboard_artifacts.artifact_type_id = " + TSK_ACCOUNT.getTypeID() + "\n"
343  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.Type.TSK_ACCOUNT_TYPE.getTypeID() + "\n"
344  + getFilterByDataSourceClause() + "\n"
345  + " -- group by artifact_id to ensure only one account type per artifact\n"
346  + " GROUP BY blackboard_artifacts.artifact_id\n"
347  + ") res\n"
348  + "GROUP BY res.account_type";
349 
350  try (SleuthkitCase.CaseDbQuery executeQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(accountTypesInUseQuery);
351  ResultSet resultSet = executeQuery.getResultSet()) {
352 
353  counts.clear();
354  while (resultSet.next()) {
355  String accountType = resultSet.getString("account_type");
356  Long count = resultSet.getLong("count");
357  counts.put(accountType, count);
358  }
359  } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
360  LOGGER.log(Level.SEVERE, "Error querying for account_types", ex);
361  }
362  }
363  }
364 
368  private class AccountTypeFactory extends ObservingChildren<String> {
369 
370  /*
371  * The pcl is in this class because it has the easiest mechanisms to add
372  * and remove itself during its life cycles.
373  */
374  private final PropertyChangeListener pcl = new PropertyChangeListener() {
375  @Override
376  public void propertyChange(PropertyChangeEvent evt) {
377  String eventType = evt.getPropertyName();
378  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
385  try {
393  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
394  if (null != eventData
395  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
396  accountTypeResults.update();
397  reviewStatusBus.post(eventData);
398  }
399  } catch (NoCurrentCaseException notUsed) {
400  // Case is closed, do nothing.
401  }
402  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
403  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
410  try {
412  accountTypeResults.update();
413  refresh(true);
414  } catch (NoCurrentCaseException notUsed) {
415  // Case is closed, do nothing.
416  }
417  }
418  }
419  };
420 
421  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
422 
423  @Subscribe
424  @Override
425  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
426  refresh(true);
427  }
428 
429  @Subscribe
430  @Override
431  void handleDataAdded(ModuleDataEvent event) {
432  refresh(true);
433  }
434 
435  @Override
436  protected boolean createKeys(List<String> list) {
437  list.addAll(accountTypeResults.getTypes());
438  return true;
439  }
440 
449  private Node[] getNodeArr(Node node) {
450  reviewStatusBus.register(node);
451  return new Node[]{node};
452  }
453 
454  @Override
455  protected Node[] createNodesForKey(String accountTypeName) {
456 
457  if (Account.Type.CREDIT_CARD.getTypeName().equals(accountTypeName)) {
459  } else {
460 
461  try {
462  Account.Type accountType = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().getAccountType(accountTypeName);
463  if (accountType != null) {
464  return getNodeArr(new DefaultAccountTypeNode(accountType));
465  } else {
466  // This can only happen if a TSK_ACCOUNT artifact was created not using CommunicationManager
467  LOGGER.log(Level.SEVERE, "Unknown account type '" + accountTypeName + "' found - account will not be displayed.\n"
468  + "Account type names must match an entry in the display_name column of the account_types table.\n"
469  + "Accounts should be created using the CommunicationManager API.");
470  }
471  } catch (TskCoreException | NoCurrentCaseException ex) {
472  LOGGER.log(Level.SEVERE, "Error getting display name for account type. ", ex);
473  }
474 
475  return new Node[]{};
476  }
477  }
478 
479  @Override
480  protected void finalize() throws Throwable {
484  super.finalize();
485  }
486 
487  @Override
488  protected void addNotify() {
492  super.addNotify();
493  refresh(true);
494  }
495 
496  }
497 
498  final private class DefaultAccountFactory extends ObservingChildren<Long> {
499 
500  private final Account.Type accountType;
501 
502  private DefaultAccountFactory(Account.Type accountType) {
503  this.accountType = accountType;
504  }
505 
506  private final PropertyChangeListener pcl = new PropertyChangeListener() {
507  @Override
508  public void propertyChange(PropertyChangeEvent evt) {
509  String eventType = evt.getPropertyName();
510  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
517  try {
525  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
526  if (null != eventData
527  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
528  reviewStatusBus.post(eventData);
529  }
530  } catch (NoCurrentCaseException notUsed) {
531  // Case is closed, do nothing.
532  }
533  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
534  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
541  try {
543  refresh(true);
544 
545  } catch (NoCurrentCaseException notUsed) {
546  // Case is closed, do nothing.
547  }
548  }
549  }
550  };
551 
552  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
553 
554  @Override
555  protected void addNotify() {
559  }
560 
561  @Override
562  protected void finalize() throws Throwable {
563  super.finalize();
567  }
568 
569  @Override
570  protected boolean createKeys(List<Long> list) {
571  String query
572  = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS
573  + " FROM blackboard_artifacts " //NON-NLS
574  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
575  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
576  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
577  + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS
579  + getRejectedArtifactFilterClause(); //NON-NLS
580  try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query);
581  ResultSet rs = results.getResultSet();) {
582  List<Long> tempList = new ArrayList<>();
583  while (rs.next()) {
584  tempList.add(rs.getLong("artifact_obj_id")); // NON-NLS
585  }
586  list.addAll(tempList);
587  } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
588  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
589  }
590 
591  return true;
592  }
593 
594  @Override
595  protected Node[] createNodesForKey(Long t) {
596  try {
597  return new Node[]{new BlackboardArtifactNode(Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().getDataArtifactById(t))};
598  } catch (TskCoreException | NoCurrentCaseException ex) {
599  LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex);
600  return new Node[0];
601  }
602  }
603 
604  @Subscribe
605  @Override
606  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
607  refresh(true);
608  }
609 
610  @Subscribe
611  @Override
612  void handleDataAdded(ModuleDataEvent event) {
613  refresh(true);
614  }
615  }
616 
621  final public class DefaultAccountTypeNode extends DisplayableItemNode {
622 
623  private final Account.Type accountType;
624 
625  private DefaultAccountTypeNode(Account.Type accountType) {
626  super(Children.create(new DefaultAccountFactory(accountType), true), Lookups.singleton(accountType));
627  this.accountType = accountType;
628  String iconPath = getIconFilePath(accountType);
629  this.setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath); //NON-NLS
630  setName(accountType.getTypeName());
631  updateName();
632  }
633 
634  @Override
635  public boolean isLeafTypeNode() {
636  return true;
637  }
638 
639  @Override
640  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
641  return visitor.visit(this);
642  }
643 
644  @Override
645  public String getItemType() {
646  return getClass().getName();
647  }
648 
649  @Subscribe
650  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
651  updateName();
652  }
653 
654  @Subscribe
655  void handleDataAdded(ModuleDataEvent event) {
656  updateName();
657  }
658 
663  public void updateName() {
664  setDisplayName(String.format("%s (%d)", accountType.getDisplayName(), accountTypeResults.getCount(accountType.getTypeName())));
665  }
666  }
667 
671  private enum CreditCardViewMode {
674  }
675 
676  final private class ViewModeFactory extends ObservingChildren<CreditCardViewMode> {
677 
678  private final PropertyChangeListener pcl = new PropertyChangeListener() {
679  @Override
680  public void propertyChange(PropertyChangeEvent evt) {
681  String eventType = evt.getPropertyName();
682  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
689  try {
697  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
698  if (null != eventData
699  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
700  reviewStatusBus.post(eventData);
701  }
702  } catch (NoCurrentCaseException notUsed) {
703  // Case is closed, do nothing.
704  }
705  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
706  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
713  try {
715  refresh(true);
716 
717  } catch (NoCurrentCaseException notUsed) {
718  // Case is closed, do nothing.
719  }
720  }
721  }
722  };
723 
724  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
725 
726  @Subscribe
727  @Override
728  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
729  refresh(true);
730  }
731 
732  @Subscribe
733  @Override
734  void handleDataAdded(ModuleDataEvent event) {
735  refresh(true);
736  }
737 
738  @Override
739  protected void addNotify() {
743  super.addNotify();
744  }
745 
746  @Override
747  protected void finalize() throws Throwable {
748  super.finalize();
752  super.removeNotify();
753  }
754 
758  @Override
759  protected boolean createKeys(List<CreditCardViewMode> list) {
760  list.addAll(Arrays.asList(CreditCardViewMode.values()));
761 
762  return true;
763  }
764 
765  @Override
766  protected Node[] createNodesForKey(CreditCardViewMode key) {
767  switch (key) {
768  case BY_BIN:
769  return new Node[]{new ByBINNode()};
770  case BY_FILE:
771  return new Node[]{new ByFileNode()};
772  default:
773  return new Node[0];
774  }
775  }
776  }
777 
782 
788  super(Children.create(new ViewModeFactory(), true), Lookups.singleton(Account.Type.CREDIT_CARD.getDisplayName()));
789  setName(Account.Type.CREDIT_CARD.getDisplayName());
790  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS
791  }
792 
797  public void updateName() {
798  setName(String.format("%s (%d)", Account.Type.CREDIT_CARD.getDisplayName(), accountTypeResults.getCount(Account.Type.CREDIT_CARD.getTypeName())));
799  }
800 
801  @Subscribe
802  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
803  updateName();
804  }
805 
806  @Subscribe
807  void handleDataAdded(ModuleDataEvent event) {
808  updateName();
809  }
810 
811  @Override
812  public boolean isLeafTypeNode() {
813  return false;
814  }
815 
816  @Override
817  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
818  return visitor.visit(this);
819  }
820 
821  @Override
822  public String getItemType() {
823  return getClass().getName();
824  }
825  }
826 
827  final private class FileWithCCNFactory extends ObservingChildren<FileWithCCN> {
828 
829  private final PropertyChangeListener pcl = new PropertyChangeListener() {
830  @Override
831  public void propertyChange(PropertyChangeEvent evt) {
832  String eventType = evt.getPropertyName();
833  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
840  try {
848  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
849  if (null != eventData
850  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
851  reviewStatusBus.post(eventData);
852  }
853  } catch (NoCurrentCaseException notUsed) {
854  // Case is closed, do nothing.
855  }
856  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
857  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
864  try {
866  refresh(true);
867 
868  } catch (NoCurrentCaseException notUsed) {
869  // Case is closed, do nothing.
870  }
871  }
872  }
873  };
874 
875  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
876 
877  @Override
878  protected void addNotify() {
882  super.addNotify();
883  }
884 
885  @Override
886  protected void finalize() throws Throwable {
887  super.finalize();
891  }
892 
893  @Subscribe
894  @Override
895  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
896  refresh(true);
897  }
898 
899  @Subscribe
900  @Override
901  void handleDataAdded(ModuleDataEvent event) {
902  refresh(true);
903  }
904 
905  @Override
906  protected boolean createKeys(List<FileWithCCN> list) {
907  try {
908  String query
909  = "SELECT blackboard_artifacts.obj_id," //NON-NLS
910  + " solr_attribute.value_text AS solr_document_id, "; //NON-NLS
911  if (Case.getCurrentCaseThrows().getSleuthkitCase().getDatabaseType().equals(DbType.POSTGRESQL)) {
912  query += " string_agg(blackboard_artifacts.artifact_id::character varying, ',') AS artifact_IDs, " //NON-NLS
913  + " string_agg(blackboard_artifacts.review_status_id::character varying, ',') AS review_status_ids, ";
914  } else {
915  query += " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS
916  + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids, ";
917  }
918  query += " COUNT( blackboard_artifacts.artifact_id) AS hits " //NON-NLS
919  + " FROM blackboard_artifacts " //NON-NLS
920  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
921  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
922  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
923  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
924  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
925  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
928  + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS
929  + " ORDER BY hits DESC "; //NON-NLS
930  try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query);
931  ResultSet resultSet = results.getResultSet();) {
932  while (resultSet.next()) {
933  long file_id = resultSet.getLong("obj_id");
934  AbstractFile abstractFileById = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(file_id);
935  if (abstractFileById != null) {
936  list.add(new FileWithCCN(
937  abstractFileById,
938  file_id, //NON-NLS
939  resultSet.getString("solr_document_id"), //NON-NLS
940  unGroupConcat(resultSet.getString("artifact_IDs"), Long::valueOf), //NON-NLS
941  resultSet.getLong("hits"), //NON-NLS
942  new HashSet<>(unGroupConcat(resultSet.getString("review_status_ids"), reviewStatusID -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(reviewStatusID)))))); //NON-NLS
943  }
944  }
945  } catch (TskCoreException | SQLException ex) {
946  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
947  }
948 
949  } catch (NoCurrentCaseException ex) {
950  LOGGER.log(Level.SEVERE, "Error getting case.", ex);
951  }
952  return true;
953  }
954 
955  @Override
956  protected Node[] createNodesForKey(FileWithCCN key) {
957  //add all account artifacts for the file and the file itself to the lookup
958  try {
959  List<Object> lookupContents = new ArrayList<>();
960  for (long artId : key.artifactIDs) {
961  lookupContents.add(Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(artId));
962  }
963  AbstractFile abstractFileById = key.getFile();
964  lookupContents.add(abstractFileById);
965  return new Node[]{new FileWithCCNNode(key, abstractFileById, lookupContents.toArray())};
966  } catch (TskCoreException | NoCurrentCaseException ex) {
967  LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS
968  return new Node[0];
969  }
970  }
971  }
972 
977  final public class ByFileNode extends DisplayableItemNode {
978 
982  private ByFileNode() {
983  super(Children.create(new FileWithCCNFactory(), true), Lookups.singleton("By File"));
984  setName("By File"); //NON-NLS
985  updateDisplayName();
986  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS
987  reviewStatusBus.register(this);
988  }
989 
990  @NbBundle.Messages({
991  "# {0} - number of children",
992  "Accounts.ByFileNode.displayName=By File ({0})"})
993  private void updateDisplayName() {
994  String query
995  = "SELECT count(*) FROM ( SELECT count(*) AS documents "
996  + " FROM blackboard_artifacts " //NON-NLS
997  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
998  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
999  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
1000  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
1001  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
1002  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1005  + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo";
1006  try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query);
1007  ResultSet resultSet = results.getResultSet();) {
1008  while (resultSet.next()) {
1009  if (Case.getCurrentCaseThrows().getSleuthkitCase().getDatabaseType().equals(DbType.POSTGRESQL)) {
1010  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count")));
1011  } else {
1012  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count(*)")));
1013  }
1014  }
1015  } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
1016  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
1017 
1018  }
1019  }
1020 
1021  @Override
1022  public boolean isLeafTypeNode() {
1023  return true;
1024  }
1025 
1026  @Override
1027  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1028  return visitor.visit(this);
1029  }
1030 
1031  @Override
1032  public String getItemType() {
1033  return getClass().getName();
1034  }
1035 
1036  @Subscribe
1037  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1038  updateDisplayName();
1039  }
1040 
1041  @Subscribe
1042  void handleDataAdded(ModuleDataEvent event) {
1043  updateDisplayName();
1044  }
1045  }
1046 
1047  final private class BINFactory extends ObservingChildren<BinResult> {
1048 
1049  private final PropertyChangeListener pcl = new PropertyChangeListener() {
1050  @Override
1051  public void propertyChange(PropertyChangeEvent evt) {
1052  String eventType = evt.getPropertyName();
1053  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
1060  try {
1068  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
1069  if (null != eventData
1070  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
1071  reviewStatusBus.post(eventData);
1072  }
1073  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
1074  // Case is closed, do nothing.
1075  }
1076  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
1077  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
1084  try {
1086 
1087  refresh(true);
1088  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
1089  // Case is closed, do nothing.
1090  }
1091  }
1092  }
1093  };
1094 
1095  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
1096 
1097  @Override
1098  protected void addNotify() {
1102  super.addNotify();
1103  }
1104 
1105  @Override
1106  protected void finalize() throws Throwable {
1107  super.finalize();
1111  }
1112 
1113  @Subscribe
1114  @Override
1115  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1116  refresh(true);
1117  }
1118 
1119  @Subscribe
1120  @Override
1121  void handleDataAdded(ModuleDataEvent event) {
1122  refresh(true);
1123  }
1124 
1125  @Override
1126  protected boolean createKeys(List<BinResult> list) {
1127 
1128  RangeMap<Integer, BinResult> binRanges = TreeRangeMap.create();
1129 
1130  String query
1131  = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS
1132  + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS
1133  + " FROM blackboard_artifacts " //NON-NLS
1134  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
1135  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1136  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1139  + " GROUP BY BIN " //NON-NLS
1140  + " ORDER BY BIN "; //NON-NLS
1141  try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query);
1142  ResultSet resultSet = results.getResultSet();) {
1143  //sort all te individual bins in to the ranges
1144  while (resultSet.next()) {
1145  final Integer bin = Integer.valueOf(resultSet.getString("BIN"));
1146  long count = resultSet.getLong("count");
1147 
1148  BINRange binRange = (BINRange) CreditCards.getBINInfo(bin);
1149  BinResult previousResult = binRanges.get(bin);
1150 
1151  if (previousResult != null) {
1152  binRanges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd()));
1153  count += previousResult.getCount();
1154  }
1155 
1156  if (binRange == null) {
1157  binRanges.put(Range.closed(bin, bin), new BinResult(count, bin, bin));
1158  } else {
1159  binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange));
1160  }
1161  }
1162  binRanges.asMapOfRanges().values().forEach(list::add);
1163  } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
1164  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1165  }
1166 
1167  return true;
1168  }
1169 
1170  @Override
1171  protected Node[] createNodesForKey(BinResult key) {
1172  return new Node[]{new BINNode(key)};
1173  }
1174  }
1175 
1180  final public class ByBINNode extends DisplayableItemNode {
1181 
1185  @NbBundle.Messages("Accounts.ByBINNode.name=By BIN")
1186  private ByBINNode() {
1187  super(Children.create(new BINFactory(), true), Lookups.singleton(Bundle.Accounts_ByBINNode_name()));
1188  setName(Bundle.Accounts_ByBINNode_name()); //NON-NLS
1189  updateDisplayName();
1190  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1191  reviewStatusBus.register(this);
1192  }
1193 
1194  @NbBundle.Messages({
1195  "# {0} - number of children",
1196  "Accounts.ByBINNode.displayName=By BIN ({0})"})
1197  private void updateDisplayName() {
1198  String query
1199  = "SELECT count(distinct SUBSTR(blackboard_attributes.value_text,1,8)) AS BINs " //NON-NLS
1200  + " FROM blackboard_artifacts " //NON-NLS
1201  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
1202  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1203  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1205  + getRejectedArtifactFilterClause(); //NON-NLS
1206  try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query);
1207  ResultSet resultSet = results.getResultSet();) {
1208  while (resultSet.next()) {
1209  setDisplayName(Bundle.Accounts_ByBINNode_displayName(resultSet.getLong("BINs")));
1210  }
1211  } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
1212  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1213  }
1214  }
1215 
1216  @Override
1217  public boolean isLeafTypeNode() {
1218  return false;
1219  }
1220 
1221  @Override
1222  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1223  return visitor.visit(this);
1224  }
1225 
1226  @Override
1227  public String getItemType() {
1228  return getClass().getName();
1229  }
1230 
1231  @Subscribe
1232  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1233  updateDisplayName();
1234  }
1235 
1236  @Subscribe
1237  void handleDataAdded(ModuleDataEvent event) {
1238  updateDisplayName();
1239  }
1240  }
1241 
1246  @Immutable
1247  final private static class FileWithCCN {
1248 
1249  @Override
1250  public int hashCode() {
1251  int hash = 5;
1252  hash = 79 * hash + (int) (this.objID ^ (this.objID >>> 32));
1253  hash = 79 * hash + Objects.hashCode(this.keywordSearchDocID);
1254  hash = 79 * hash + Objects.hashCode(this.artifactIDs);
1255  hash = 79 * hash + (int) (this.hits ^ (this.hits >>> 32));
1256  hash = 79 * hash + Objects.hashCode(this.statuses);
1257  return hash;
1258  }
1259 
1260  @Override
1261  public boolean equals(Object obj) {
1262  if (this == obj) {
1263  return true;
1264  }
1265  if (obj == null) {
1266  return false;
1267  }
1268  if (getClass() != obj.getClass()) {
1269  return false;
1270  }
1271  final FileWithCCN other = (FileWithCCN) obj;
1272  if (this.objID != other.objID) {
1273  return false;
1274  }
1275  if (this.hits != other.hits) {
1276  return false;
1277  }
1278  if (!Objects.equals(this.keywordSearchDocID, other.keywordSearchDocID)) {
1279  return false;
1280  }
1281  if (!Objects.equals(this.artifactIDs, other.artifactIDs)) {
1282  return false;
1283  }
1284  if (!Objects.equals(this.statuses, other.statuses)) {
1285  return false;
1286  }
1287  return true;
1288  }
1289 
1290  private final long objID;
1291  private final String keywordSearchDocID;
1292  private final List<Long> artifactIDs;
1293  private final long hits;
1294  private final Set<BlackboardArtifact.ReviewStatus> statuses;
1295  private final AbstractFile file;
1296 
1297  private FileWithCCN(AbstractFile file, long objID, String solrDocID, List<Long> artifactIDs, long hits, Set<BlackboardArtifact.ReviewStatus> statuses) {
1298  this.objID = objID;
1299  this.keywordSearchDocID = solrDocID;
1300  this.artifactIDs = artifactIDs;
1301  this.hits = hits;
1302  this.statuses = statuses;
1303  this.file = file;
1304  }
1305 
1311  public long getObjID() {
1312  return objID;
1313  }
1314 
1321  public String getkeywordSearchDocID() {
1322  return keywordSearchDocID;
1323  }
1324 
1330  public List<Long> getArtifactIDs() {
1331  return Collections.unmodifiableList(artifactIDs);
1332  }
1333 
1339  public long getHits() {
1340  return hits;
1341  }
1342 
1348  public Set<BlackboardArtifact.ReviewStatus> getStatuses() {
1349  return Collections.unmodifiableSet(statuses);
1350  }
1351 
1352  AbstractFile getFile() {
1353  return file;
1354  }
1355  }
1356 
1373  static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
1374  return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
1375  : Stream.of(groupConcat.split(",")) //NON-NLS
1376  .map(mapper::apply)
1377  .collect(Collectors.toList());
1378  }
1379 
1383  final public class FileWithCCNNode extends DisplayableItemNode {
1384 
1385  private final FileWithCCN fileKey;
1386  private final String fileName;
1387 
1397  @NbBundle.Messages({
1398  "# {0} - raw file name",
1399  "# {1} - solr chunk id",
1400  "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"})
1401  private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) {
1402  super(Children.LEAF, Lookups.fixed(lookupContents));
1403  this.fileKey = key;
1404  this.fileName = (key.getkeywordSearchDocID() == null)
1405  ? content.getName()
1406  : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getkeywordSearchDocID(), "_")); //NON-NLS
1407  setName(fileName + key.getObjID());
1408  setDisplayName(fileName);
1409  }
1410 
1411  @Override
1412  public boolean isLeafTypeNode() {
1413  return true;
1414  }
1415 
1416  @Override
1417  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1418  return visitor.visit(this);
1419  }
1420 
1421  @Override
1422  public String getItemType() {
1423  return getClass().getName();
1424  }
1425 
1426  @Override
1427  @NbBundle.Messages({
1428  "Accounts.FileWithCCNNode.nameProperty.displayName=File",
1429  "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts",
1430  "Accounts.FileWithCCNNode.statusProperty.displayName=Status",
1431  "Accounts.FileWithCCNNode.noDescription=no description"})
1432  protected Sheet createSheet() {
1433  Sheet sheet = super.createSheet();
1434  Sheet.Set propSet = sheet.get(Sheet.PROPERTIES);
1435  if (propSet == null) {
1436  propSet = Sheet.createPropertiesSet();
1437  sheet.put(propSet);
1438  }
1439 
1440  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1441  Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1442  Bundle.Accounts_FileWithCCNNode_noDescription(),
1443  fileName));
1444  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1445  Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1446  Bundle.Accounts_FileWithCCNNode_noDescription(),
1447  fileKey.getHits()));
1448  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1449  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1450  Bundle.Accounts_FileWithCCNNode_noDescription(),
1451  fileKey.getStatuses().stream()
1452  .map(BlackboardArtifact.ReviewStatus::getDisplayName)
1453  .collect(Collectors.joining(", ")))); //NON-NLS
1454 
1455  return sheet;
1456  }
1457 
1458  @Override
1459  public Action[] getActions(boolean context) {
1460  Action[] actions = super.getActions(context);
1461  ArrayList<Action> arrayList = new ArrayList<>();
1462  try {
1463  arrayList.addAll(DataModelActionsFactory.getActions(Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(fileKey.getObjID()), false));
1464  } catch (TskCoreException | NoCurrentCaseException ex) {
1465  LOGGER.log(Level.SEVERE, "Error gettung content by id", ex);
1466  }
1467 
1468  arrayList.add(approveActionInstance);
1469  arrayList.add(rejectActionInstance);
1470  arrayList.add(null);
1471  arrayList.addAll(Arrays.asList(actions));
1472  return arrayList.toArray(new Action[arrayList.size()]);
1473  }
1474  }
1475 
1476  final private class CreditCardNumberFactory extends ObservingChildren<DataArtifact> {
1477 
1478  private final BinResult bin;
1479 
1481  this.bin = bin;
1482  }
1483 
1484  @Subscribe
1485  @Override
1486  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1487  refresh(true);
1488  }
1489 
1490  @Subscribe
1491  @Override
1492  void handleDataAdded(ModuleDataEvent event) {
1493  refresh(true);
1494  }
1495 
1496  @Override
1497  protected boolean createKeys(List<DataArtifact> list) {
1498 
1499  String query
1500  = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS
1501  + " FROM blackboard_artifacts " //NON-NLS
1502  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1503  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1504  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1505  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1508  + " ORDER BY blackboard_attributes.value_text"; //NON-NLS
1509  try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query);
1510  ResultSet rs = results.getResultSet();) {
1511  while (rs.next()) {
1512  list.add(Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().getDataArtifactById(rs.getLong("artifact_obj_id"))); //NON-NLS
1513  }
1514  } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
1515  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1516 
1517  }
1518  return true;
1519  }
1520 
1521  @Override
1522  protected Node[] createNodesForKey(DataArtifact artifact) {
1523  return new Node[]{new AccountArtifactNode(artifact)};
1524  }
1525  }
1526 
1527  private String getBinRangeString(BinResult bin) {
1528  if (bin.getBINStart() == bin.getBINEnd()) {
1529  return Integer.toString(bin.getBINStart());
1530  } else {
1531  return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + "");
1532  }
1533  }
1534 
1535  final public class BINNode extends DisplayableItemNode {
1536 
1540  private final BinResult bin;
1541 
1542  private BINNode(BinResult bin) {
1543  super(Children.create(new CreditCardNumberFactory(bin), true), Lookups.singleton(getBinRangeString(bin)));
1544  this.bin = bin;
1545  setName(getBinRangeString(bin));
1546  updateDisplayName();
1547  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1548  reviewStatusBus.register(this);
1549  }
1550 
1551  @Subscribe
1552  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1553  updateDisplayName();
1554  updateSheet();
1555  }
1556 
1557  @Subscribe
1558  void handleDataAdded(ModuleDataEvent event) {
1559  updateDisplayName();
1560  }
1561 
1562  private void updateDisplayName() {
1563  String query
1564  = "SELECT count(blackboard_artifacts.artifact_id ) AS count" //NON-NLS
1565  + " FROM blackboard_artifacts " //NON-NLS
1566  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1567  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1568  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1569  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1572  try (SleuthkitCase.CaseDbQuery results = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query);
1573  ResultSet resultSet = results.getResultSet();) {
1574  while (resultSet.next()) {
1575  setDisplayName(getBinRangeString(bin) + " (" + resultSet.getLong("count") + ")"); //NON-NLS
1576  }
1577  } catch (TskCoreException | SQLException | NoCurrentCaseException ex) {
1578  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1579 
1580  }
1581 
1582  }
1583 
1584  @Override
1585  public boolean isLeafTypeNode() {
1586  return true;
1587  }
1588 
1589  @Override
1590  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1591  return visitor.visit(this);
1592  }
1593 
1594  @Override
1595  public String getItemType() {
1596  return getClass().getName();
1597  }
1598 
1599  private Sheet.Set getPropertySet(Sheet sheet) {
1600  Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
1601  if (sheetSet == null) {
1602  sheetSet = Sheet.createPropertiesSet();
1603  sheet.put(sheetSet);
1604  }
1605  return sheetSet;
1606  }
1607 
1608  @Override
1609  @NbBundle.Messages({
1610  "Accounts.BINNode.binProperty.displayName=Bank Identifier Number",
1611  "Accounts.BINNode.accountsProperty.displayName=Accounts",
1612  "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type",
1613  "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme",
1614  "Accounts.BINNode.brandProperty.displayName=Brand",
1615  "Accounts.BINNode.bankProperty.displayName=Bank",
1616  "Accounts.BINNode.bankCityProperty.displayName=Bank City",
1617  "Accounts.BINNode.bankCountryProperty.displayName=Bank Country",
1618  "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #",
1619  "Accounts.BINNode.bankURLProperty.displayName=Bank URL",
1620  "Accounts.BINNode.noDescription=no description"})
1621  protected Sheet createSheet() {
1622  Sheet sheet = super.createSheet();
1623  Sheet.Set properties = getPropertySet(sheet);
1624 
1625  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(),
1626  Bundle.Accounts_BINNode_binProperty_displayName(),
1627  Bundle.Accounts_BINNode_noDescription(),
1628  getBinRangeString(bin)));
1629  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(),
1630  Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1631  bin.getCount()));
1632 
1633  //add optional properties if they are available
1634  if (bin.hasDetails()) {
1635  bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(),
1636  Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1637  cardType)));
1638  bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(),
1639  Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1640  scheme)));
1641  bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(),
1642  Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1643  brand)));
1644  bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(),
1645  Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1646  bankName)));
1647  bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(),
1648  Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1649  bankCity)));
1650  bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(),
1651  Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1652  country)));
1653  bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(),
1654  Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1655  phoneNumber)));
1656  bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(),
1657  Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1658  url)));
1659  }
1660  return sheet;
1661  }
1662 
1663  private void updateSheet() {
1664  SwingUtilities.invokeLater(() -> {
1665  this.setSheet(createSheet());
1666  });
1667  }
1668 
1669  }
1670 
1675  @Immutable
1676  final static private class BinResult implements CreditCards.BankIdentificationNumber {
1677 
1678  @Override
1679  public int hashCode() {
1680  int hash = 3;
1681  hash = 97 * hash + this.binEnd;
1682  hash = 97 * hash + this.binStart;
1683  return hash;
1684  }
1685 
1686  @Override
1687  public boolean equals(Object obj) {
1688  if (this == obj) {
1689  return true;
1690  }
1691  if (obj == null) {
1692  return false;
1693  }
1694  if (getClass() != obj.getClass()) {
1695  return false;
1696  }
1697  final BinResult other = (BinResult) obj;
1698  if (this.binEnd != other.binEnd) {
1699  return false;
1700  }
1701  if (this.binStart != other.binStart) {
1702  return false;
1703  }
1704  return true;
1705  }
1706 
1710  private final long count;
1711 
1712  private final BINRange binRange;
1713  private final int binEnd;
1714  private final int binStart;
1715 
1716  private BinResult(long count, @Nonnull BINRange binRange) {
1717  this.count = count;
1718  this.binRange = binRange;
1719  binStart = binRange.getBINstart();
1720  binEnd = binRange.getBINend();
1721  }
1722 
1723  private BinResult(long count, int start, int end) {
1724  this.count = count;
1725  this.binRange = null;
1726  binStart = start;
1727  binEnd = end;
1728  }
1729 
1730  int getBINStart() {
1731  return binStart;
1732  }
1733 
1734  int getBINEnd() {
1735  return binEnd;
1736  }
1737 
1738  long getCount() {
1739  return count;
1740  }
1741 
1742  boolean hasDetails() {
1743  return binRange != null;
1744  }
1745 
1746  @Override
1747  public Optional<Integer> getNumberLength() {
1748  return binRange.getNumberLength();
1749  }
1750 
1751  @Override
1752  public Optional<String> getBankCity() {
1753  return binRange.getBankCity();
1754  }
1755 
1756  @Override
1757  public Optional<String> getBankName() {
1758  return binRange.getBankName();
1759  }
1760 
1761  @Override
1762  public Optional<String> getBankPhoneNumber() {
1763  return binRange.getBankPhoneNumber();
1764  }
1765 
1766  @Override
1767  public Optional<String> getBankURL() {
1768  return binRange.getBankURL();
1769  }
1770 
1771  @Override
1772  public Optional<String> getBrand() {
1773  return binRange.getBrand();
1774  }
1775 
1776  @Override
1777  public Optional<String> getCardType() {
1778  return binRange.getCardType();
1779  }
1780 
1781  @Override
1782  public Optional<String> getCountry() {
1783  return binRange.getCountry();
1784  }
1785 
1786  @Override
1787  public Optional<String> getScheme() {
1788  return binRange.getScheme();
1789  }
1790  }
1791 
1792  final private class AccountArtifactNode extends BlackboardArtifactNode {
1793 
1794  private final BlackboardArtifact artifact;
1795 
1796  private AccountArtifactNode(BlackboardArtifact artifact) {
1797  super(artifact, "org/sleuthkit/autopsy/images/credit-card.png"); //NON-NLS
1798  this.artifact = artifact;
1799  setName(Long.toString(this.artifact.getArtifactID()));
1800 
1801  reviewStatusBus.register(this);
1802  }
1803 
1804  @Override
1805  public Action[] getActions(boolean context) {
1806  List<Action> actionsList = new ArrayList<>();
1807  actionsList.addAll(Arrays.asList(super.getActions(context)));
1808 
1809  actionsList.add(approveActionInstance);
1810  actionsList.add(rejectActionInstance);
1811 
1812  return actionsList.toArray(new Action[actionsList.size()]);
1813  }
1814 
1815  @Override
1816  protected Sheet createSheet() {
1817  Sheet sheet = super.createSheet();
1818  Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
1819  if (properties == null) {
1820  properties = Sheet.createPropertiesSet();
1821  sheet.put(properties);
1822  }
1823  properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1824  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1825  Bundle.Accounts_FileWithCCNNode_noDescription(),
1826  artifact.getReviewStatus().getDisplayName()));
1827 
1828  return sheet;
1829  }
1830 
1831  @Subscribe
1832  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1833 
1834  // Update the node if event includes this artifact
1835  event.artifacts.stream().filter((art) -> (art.getArtifactID() == this.artifact.getArtifactID())).map((_item) -> {
1836  return _item;
1837  }).forEachOrdered((_item) -> {
1838  updateSheet();
1839  });
1840  }
1841 
1842  private void updateSheet() {
1843  SwingUtilities.invokeLater(() -> {
1844  this.setSheet(createSheet());
1845  });
1846  }
1847 
1848  }
1849 
1850  @Deprecated
1851  private final class ToggleShowRejected extends AbstractAction {
1852 
1853  @NbBundle.Messages("ToggleShowRejected.name=Show Rejected Results")
1854  ToggleShowRejected() {
1855  super(Bundle.ToggleShowRejected_name());
1856  }
1857 
1858  @Override
1859  public void actionPerformed(ActionEvent e) {
1860  showRejected = !showRejected;
1861  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1862  }
1863  }
1864 
1870  public void setShowRejected(boolean showRejected) {
1871  this.showRejected = showRejected;
1872  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1873  }
1874 
1875  private abstract class ReviewStatusAction extends AbstractAction {
1876 
1877  private final BlackboardArtifact.ReviewStatus newStatus;
1878 
1879  private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) {
1880  super(displayName);
1881  this.newStatus = newStatus;
1882 
1883  }
1884 
1885  @Override
1886  public void actionPerformed(ActionEvent e) {
1887 
1888  /*
1889  * get paths for selected nodes to reselect after applying review
1890  * status change
1891  */
1892  List<String[]> selectedPaths = Utilities.actionsGlobalContext().lookupAll(Node.class).stream()
1893  .map(node -> {
1894  String[] createPath;
1895  /*
1896  * If the we are rejecting and not showing rejected
1897  * results, then the selected node, won't exist any
1898  * more, so we select the previous one in stead.
1899  */
1900  if (newStatus == BlackboardArtifact.ReviewStatus.REJECTED && showRejected == false) {
1901  List<Node> siblings = Arrays.asList(node.getParentNode().getChildren().getNodes());
1902  if (siblings.size() > 1) {
1903  int indexOf = siblings.indexOf(node);
1904  //there is no previous for the first node, so instead we select the next one
1905  Node sibling = indexOf > 0
1906  ? siblings.get(indexOf - 1)
1907  : siblings.get(Integer.max(indexOf + 1, siblings.size() - 1));
1908  createPath = NodeOp.createPath(sibling, null);
1909  } else {
1910  /*
1911  * if there are no other siblings to select,
1912  * just return null, but note we need to filter
1913  * this out of stream below
1914  */
1915  return null;
1916  }
1917  } else {
1918  createPath = NodeOp.createPath(node, null);
1919  }
1920  //for the reselect to work we need to strip off the first part of the path.
1921  return Arrays.copyOfRange(createPath, 1, createPath.length);
1922  })
1923  .filter(Objects::nonNull)
1924  .collect(Collectors.toList());
1925 
1926  //change status of selected artifacts
1927  final Collection<? extends BlackboardArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
1928  artifacts.forEach(artifact -> {
1929  try {
1930  artifact.setReviewStatus(newStatus);
1931  } catch (TskCoreException ex) {
1932  LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS
1933  }
1934  });
1935  //post event
1936  reviewStatusBus.post(new ReviewStatusChangeEvent(artifacts, newStatus));
1937 
1938  final DataResultTopComponent directoryListing = DirectoryTreeTopComponent.findInstance().getDirectoryListing();
1939  final Node rootNode = directoryListing.getRootNode();
1940 
1941  //convert paths back to nodes
1942  List<Node> toArray = new ArrayList<>();
1943  selectedPaths.forEach(path -> {
1944  try {
1945  toArray.add(NodeOp.findPath(rootNode, path));
1946  } catch (NodeNotFoundException ex) { //NOPMD empty catch clause
1947  //just ingnore paths taht don't exist. this is expected since we are rejecting
1948  }
1949  });
1950  //select nodes
1951  directoryListing.setSelectedNodes(toArray.toArray(new Node[toArray.size()]));
1952  }
1953  }
1954 
1955  final private class ApproveAccounts extends ReviewStatusAction {
1956 
1957  @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"})
1958  private ApproveAccounts() {
1959  super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED);
1960  }
1961  }
1962 
1963  final private class RejectAccounts extends ReviewStatusAction {
1964 
1965  @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"})
1966  private RejectAccounts() {
1967  super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED);
1968  }
1969  }
1970 
1971  static private class ReviewStatusChangeEvent {
1972 
1973  Collection<? extends BlackboardArtifact> artifacts;
1974  BlackboardArtifact.ReviewStatus newReviewStatus;
1975 
1976  ReviewStatusChangeEvent(Collection<? extends BlackboardArtifact> artifacts, BlackboardArtifact.ReviewStatus newReviewStatus) {
1977  this.artifacts = artifacts;
1978  this.newReviewStatus = newReviewStatus;
1979  }
1980  }
1981 
1987  public static String getIconFilePath(Account.Type type) {
1988 
1989  if (type.equals(Account.Type.CREDIT_CARD)) {
1990  return ICON_BASE_PATH + "credit-card.png";
1991  } else if (type.equals(Account.Type.DEVICE)) {
1992  return ICON_BASE_PATH + "image.png";
1993  } else if (type.equals(Account.Type.EMAIL)) {
1994  return ICON_BASE_PATH + "email.png";
1995  } else if (type.equals(Account.Type.FACEBOOK)) {
1996  return ICON_BASE_PATH + "facebook.png";
1997  } else if (type.equals(Account.Type.INSTAGRAM)) {
1998  return ICON_BASE_PATH + "instagram.png";
1999  } else if (type.equals(Account.Type.MESSAGING_APP)) {
2000  return ICON_BASE_PATH + "messaging.png";
2001  } else if (type.equals(Account.Type.PHONE)) {
2002  return ICON_BASE_PATH + "phone.png";
2003  } else if (type.equals(Account.Type.TWITTER)) {
2004  return ICON_BASE_PATH + "twitter.png";
2005  } else if (type.equals(Account.Type.WEBSITE)) {
2006  return ICON_BASE_PATH + "web-file.png";
2007  } else if (type.equals(Account.Type.WHATSAPP)) {
2008  return ICON_BASE_PATH + "WhatsApp.png";
2009  } else {
2010  //there could be a default icon instead...
2011  return ICON_BASE_PATH + "face.png";
2012 // throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName());
2013  }
2014  }
2015 }
static final Set< IngestManager.IngestModuleEvent > INGEST_MODULE_EVENTS_OF_INTEREST
Definition: Accounts.java:100
boolean createKeys(List< CreditCardViewMode > list)
Definition: Accounts.java:759
BlackboardArtifact.Type getBlackboardArtifactType()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static synchronized IngestManager getInstance()
Set< BlackboardArtifact.ReviewStatus > getStatuses()
Definition: Accounts.java:1348
static List< Action > getActions(File file, boolean isArtifactSource)
static synchronized BankIdentificationNumber getBINInfo(int bin)
static String getIconFilePath(Account.Type type)
Definition: Accounts.java:1987
void removeIngestJobEventListener(final PropertyChangeListener listener)
final Set< BlackboardArtifact.ReviewStatus > statuses
Definition: Accounts.java:1294
void addIngestJobEventListener(final PropertyChangeListener listener)
BinResult(long count,@Nonnull BINRange binRange)
Definition: Accounts.java:1716
FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents)
Definition: Accounts.java:1401
FileWithCCN(AbstractFile file, long objID, String solrDocID, List< Long > artifactIDs, long hits, Set< BlackboardArtifact.ReviewStatus > statuses)
Definition: Accounts.java:1297
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:708
static final Set< IngestManager.IngestJobEvent > INGEST_JOB_EVENTS_OF_INTEREST
Definition: Accounts.java:99
ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus)
Definition: Accounts.java:1879
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:753

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