Autopsy  4.19.1
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 org.apache.commons.lang3.StringUtils;
52 import org.openide.nodes.ChildFactory;
53 import org.openide.nodes.Children;
54 import org.openide.nodes.Node;
55 import org.openide.nodes.NodeNotFoundException;
56 import org.openide.nodes.NodeOp;
57 import org.openide.nodes.Sheet;
58 import org.openide.util.NbBundle;
59 import org.openide.util.Utilities;
60 import org.openide.util.WeakListeners;
61 import org.openide.util.lookup.Lookups;
78 import org.sleuthkit.datamodel.AbstractFile;
79 import org.sleuthkit.datamodel.Account;
80 import org.sleuthkit.datamodel.BlackboardArtifact;
81 import org.sleuthkit.datamodel.BlackboardArtifact.Type;
82 import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ACCOUNT;
83 import org.sleuthkit.datamodel.BlackboardAttribute;
84 import org.sleuthkit.datamodel.Content;
85 import org.sleuthkit.datamodel.DataArtifact;
86 import org.sleuthkit.datamodel.SleuthkitCase;
87 import org.sleuthkit.datamodel.TskCoreException;
88 import org.sleuthkit.datamodel.TskData.DbType;
89 
94 final public class Accounts implements AutopsyVisitableItem {
95 
96  private static final Logger LOGGER = Logger.getLogger(Accounts.class.getName());
97  private static final String ICON_BASE_PATH = "/org/sleuthkit/autopsy/images/"; //NON-NLS
100  private static final String DISPLAY_NAME = Bundle.Accounts_RootNode_displayName();
101 
102  @NbBundle.Messages("AccountsRootNode.name=Accounts") //used for the viewArtifact navigation
103  final public static String NAME = Bundle.AccountsRootNode_name();
104 
105  private SleuthkitCase skCase;
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(SleuthkitCase skCase) {
127  this(skCase, 0);
128  }
129 
136  public Accounts(SleuthkitCase skCase, long objId) {
137  this.skCase = skCase;
138  this.filteringDSObjId = objId;
139 
140  this.rejectActionInstance = new RejectAccounts();
141  this.approveActionInstance = new ApproveAccounts();
142  this.accountTypeResults = new AccountTypeResults();
143  }
144 
145  @Override
146  public <T> T accept(AutopsyItemVisitor<T> visitor) {
147  return visitor.visit(this);
148  }
149 
158  return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS
159  }
160 
167  private String getFilterByDataSourceClause() {
168  if (filteringDSObjId > 0) {
169  return " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId + " ";
170  }
171 
172  return " ";
173  }
174 
182  @Deprecated
183  public Action newToggleShowRejectedAction() {
184  return new ToggleShowRejected();
185  }
186 
193  private abstract class ObservingChildren<X> extends ChildFactory.Detachable<X> {
194 
200  super();
201  }
202 
207  @Override
208  abstract protected boolean createKeys(List<X> list);
209 
215  @Subscribe
216  abstract void handleReviewStatusChange(ReviewStatusChangeEvent event);
217 
218  @Subscribe
219  abstract void handleDataAdded(ModuleDataEvent event);
220 
221  @Override
222  protected void finalize() throws Throwable {
223  super.finalize();
224  reviewStatusBus.unregister(ObservingChildren.this);
225  }
226 
227  @Override
228  protected void addNotify() {
229  super.addNotify();
230  refresh(true);
231  reviewStatusBus.register(ObservingChildren.this);
232  }
233  }
234 
238  @NbBundle.Messages({"Accounts.RootNode.displayName=Communication Accounts"})
239  final public class AccountsRootNode extends UpdatableCountTypeNode {
240 
241  public AccountsRootNode() {
242  super(Children.create(new AccountTypeFactory(), true),
243  Lookups.singleton(Accounts.this),
244  DISPLAY_NAME,
246  TSK_ACCOUNT);
247 
248  setName(Accounts.NAME);
249  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png"); //NON-NLS
250  }
251 
252  @Override
253  public boolean isLeafTypeNode() {
254  return false;
255  }
256 
257  @Override
258  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
259  return visitor.visit(this);
260  }
261 
262  @Override
263  public String getItemType() {
264  return getClass().getName();
265  }
266 
267  @Override
268  protected long fetchChildCount(SleuthkitCase skCase) throws TskCoreException {
269  long count = 0;
270  String dataSourceFilterClause = (filteringDSObjId > 0)
271  ? " AND " + filteringDSObjId + " IN (SELECT art.data_source_obj_id FROM blackboard_artifacts art WHERE art.artifact_id = attr.artifact_id)"
272  : "";
273 
274  String accountTypesInUseQuery
275  = "SELECT COUNT(attr.value_text) AS count"
276  + " FROM blackboard_attributes attr"
277  + " WHERE attr.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()
278  + " AND attr.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID()
279  + dataSourceFilterClause
280  + " GROUP BY attr.value_text";
281 
282  try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery);
283  ResultSet resultSet = executeQuery.getResultSet()) {
284 
285  if (resultSet.next()) {
286  count = resultSet.getLong("count");
287  }
288 
289  } catch (TskCoreException | SQLException ex) {
290  LOGGER.log(Level.SEVERE, "Error querying for count of all account types", ex);
291  }
292  return count;
293  }
294 
295  }
296 
300  private class AccountTypeResults {
301 
302  private final Map<String, Long> counts = new HashMap<>();
303 
305  update();
306  }
307 
316  Long getCount(String accountType) {
317  return counts.get(accountType);
318  }
319 
325  List<String> getTypes() {
326  List<String> types = new ArrayList<>(counts.keySet());
327  Collections.sort(types);
328  return types;
329  }
330 
334  private void update() {
335  String accountTypesInUseQuery
336  = "SELECT blackboard_attributes.value_text as account_type, COUNT(*) as count "
337  + " FROM blackboard_artifacts " //NON-NLS
338  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
339  + " WHERE blackboard_artifacts.artifact_type_id = " + TSK_ACCOUNT.getTypeID() //NON-NLS
340  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
342  + " GROUP BY blackboard_attributes.value_text ";
343 
344  try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery);
345  ResultSet resultSet = executeQuery.getResultSet()) {
346 
347  counts.clear();
348  while (resultSet.next()) {
349  String accountType = resultSet.getString("account_type");
350  Long count = resultSet.getLong("count");
351  counts.put(accountType, count);
352  }
353  } catch (TskCoreException | SQLException ex) {
354  LOGGER.log(Level.SEVERE, "Error querying for account_types", ex);
355  }
356  }
357  }
358 
362  private class AccountTypeFactory extends ObservingChildren<String> {
363 
364  /*
365  * The pcl is in this class because it has the easiest mechanisms to add
366  * and remove itself during its life cycles.
367  */
368  private final PropertyChangeListener pcl = new PropertyChangeListener() {
369  @Override
370  public void propertyChange(PropertyChangeEvent evt) {
371  String eventType = evt.getPropertyName();
372  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
379  try {
387  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
388  if (null != eventData
389  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
390  accountTypeResults.update();
391  reviewStatusBus.post(eventData);
392  }
393  } catch (NoCurrentCaseException notUsed) {
394  // Case is closed, do nothing.
395  }
396  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
397  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
404  try {
406  refresh(true);
407  } catch (NoCurrentCaseException notUsed) {
408  // Case is closed, do nothing.
409  }
410  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
411  // case was closed. Remove listeners so that we don't get called with a stale case handle
412  if (evt.getNewValue() == null) {
413  removeNotify();
414  skCase = null;
415  }
416  }
417  }
418  };
419 
420  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
421 
422  @Subscribe
423  @Override
424  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
425  refresh(true);
426  }
427 
428  @Subscribe
429  @Override
430  void handleDataAdded(ModuleDataEvent event) {
431  refresh(true);
432  }
433 
434  @Override
435  protected boolean createKeys(List<String> list) {
436  list.addAll(accountTypeResults.getTypes());
437  return true;
438  }
439 
448  private Node[] getNodeArr(Node node) {
449  reviewStatusBus.register(node);
450  return new Node[]{node};
451  }
452 
453  @Override
454  protected Node[] createNodesForKey(String accountTypeName) {
455 
456  if (Account.Type.CREDIT_CARD.getTypeName().equals(accountTypeName)) {
458  } else {
459 
460  try {
461  Account.Type accountType = skCase.getCommunicationsManager().getAccountType(accountTypeName);
462  if (accountType != null) {
463  return getNodeArr(new DefaultAccountTypeNode(accountType));
464  } else {
465  // This can only happen if a TSK_ACCOUNT artifact was created not using CommunicationManager
466  LOGGER.log(Level.SEVERE, "Unknown account type '" + accountTypeName + "' found - account will not be displayed.\n"
467  + "Account type names must match an entry in the display_name column of the account_types table.\n"
468  + "Accounts should be created using the CommunicationManager API.");
469  }
470  } catch (TskCoreException ex) {
471  LOGGER.log(Level.SEVERE, "Error getting display name for account type. ", ex);
472  }
473 
474  return new Node[]{};
475  }
476  }
477 
478  @Override
479  protected void finalize() throws Throwable {
483  super.finalize();
484  }
485 
486  @Override
487  protected void addNotify() {
491  super.addNotify();
492  refresh(true);
493  }
494 
495  }
496 
497  final private class DefaultAccountFactory extends ObservingChildren<Long> {
498 
499  private final Account.Type accountType;
500 
501  private DefaultAccountFactory(Account.Type accountType) {
502  this.accountType = accountType;
503  }
504 
505  private final PropertyChangeListener pcl = new PropertyChangeListener() {
506  @Override
507  public void propertyChange(PropertyChangeEvent evt) {
508  String eventType = evt.getPropertyName();
509  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
516  try {
524  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
525  if (null != eventData
526  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
527  reviewStatusBus.post(eventData);
528  }
529  } catch (NoCurrentCaseException notUsed) {
530  // Case is closed, do nothing.
531  }
532  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
533  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
540  try {
542  refresh(true);
543 
544  } catch (NoCurrentCaseException notUsed) {
545  // Case is closed, do nothing.
546  }
547  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
548  // case was closed. Remove listeners so that we don't get called with a stale case handle
549  if (evt.getNewValue() == null) {
550  removeNotify();
551  skCase = null;
552  }
553  }
554  }
555  };
556 
557  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
558 
559  @Override
560  protected void addNotify() {
564  }
565 
566  @Override
567  protected void finalize() throws Throwable {
568  super.finalize();
572  }
573 
574  @Override
575  protected boolean createKeys(List<Long> list) {
576  String query
577  = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS
578  + " FROM blackboard_artifacts " //NON-NLS
579  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
580  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
581  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
582  + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS
584  + getRejectedArtifactFilterClause(); //NON-NLS
585  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
586  ResultSet rs = results.getResultSet();) {
587  List<Long> tempList = new ArrayList<>();
588  while (rs.next()) {
589  tempList.add(rs.getLong("artifact_obj_id")); // NON-NLS
590  }
591  list.addAll(tempList);
592  } catch (TskCoreException | SQLException ex) {
593  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
594  }
595 
596  return true;
597  }
598 
599  @Override
600  protected Node[] createNodesForKey(Long t) {
601  try {
602  return new Node[]{new BlackboardArtifactNode(skCase.getBlackboard().getDataArtifactById(t))};
603  } catch (TskCoreException ex) {
604  LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex);
605  return new Node[0];
606  }
607  }
608 
609  @Subscribe
610  @Override
611  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
612  refresh(true);
613  }
614 
615  @Subscribe
616  @Override
617  void handleDataAdded(ModuleDataEvent event) {
618  refresh(true);
619  }
620  }
621 
626  final public class DefaultAccountTypeNode extends DisplayableItemNode {
627 
628  private final Account.Type accountType;
629 
630  private DefaultAccountTypeNode(Account.Type accountType) {
631  super(Children.create(new DefaultAccountFactory(accountType), true), Lookups.singleton(accountType));
632  this.accountType = accountType;
633  String iconPath = getIconFilePath(accountType);
634  this.setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath); //NON-NLS
635  setName(accountType.getTypeName());
636  updateName();
637  }
638 
639  @Override
640  public boolean isLeafTypeNode() {
641  return true;
642  }
643 
644  @Override
645  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
646  return visitor.visit(this);
647  }
648 
649  @Override
650  public String getItemType() {
651  return getClass().getName();
652  }
653 
654  @Subscribe
655  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
656  updateName();
657  }
658 
659  @Subscribe
660  void handleDataAdded(ModuleDataEvent event) {
661  updateName();
662  }
663 
668  public void updateName() {
669  setDisplayName(String.format("%s (%d)", accountType.getDisplayName(), accountTypeResults.getCount(accountType.getTypeName())));
670  }
671  }
672 
676  private enum CreditCardViewMode {
679  }
680 
681  final private class ViewModeFactory extends ObservingChildren<CreditCardViewMode> {
682 
683  private final PropertyChangeListener pcl = new PropertyChangeListener() {
684  @Override
685  public void propertyChange(PropertyChangeEvent evt) {
686  String eventType = evt.getPropertyName();
687  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
694  try {
702  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
703  if (null != eventData
704  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
705  reviewStatusBus.post(eventData);
706  }
707  } catch (NoCurrentCaseException notUsed) {
708  // Case is closed, do nothing.
709  }
710  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
711  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
718  try {
720  refresh(true);
721 
722  } catch (NoCurrentCaseException notUsed) {
723  // Case is closed, do nothing.
724  }
725  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
726  // case was closed. Remove listeners so that we don't get called with a stale case handle
727  if (evt.getNewValue() == null) {
728  removeNotify();
729  skCase = null;
730  }
731  }
732  }
733  };
734 
735  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
736 
737 
738  @Subscribe
739  @Override
740  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
741  refresh(true);
742  }
743 
744  @Subscribe
745  @Override
746  void handleDataAdded(ModuleDataEvent event) {
747  refresh(true);
748  }
749 
750  @Override
751  protected void addNotify() {
755  super.addNotify();
756  }
757 
758  @Override
759  protected void finalize() throws Throwable {
760  super.finalize();
764  super.removeNotify();
765  }
766 
770  @Override
771  protected boolean createKeys(List<CreditCardViewMode> list) {
772  list.addAll(Arrays.asList(CreditCardViewMode.values()));
773 
774  return true;
775  }
776 
777  @Override
778  protected Node[] createNodesForKey(CreditCardViewMode key) {
779  switch (key) {
780  case BY_BIN:
781  return new Node[]{new ByBINNode()};
782  case BY_FILE:
783  return new Node[]{new ByFileNode()};
784  default:
785  return new Node[0];
786  }
787  }
788  }
789 
794 
800  super(Children.create(new ViewModeFactory(), true), Lookups.singleton(Account.Type.CREDIT_CARD.getDisplayName()));
801  setName(Account.Type.CREDIT_CARD.getDisplayName());
802  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS
803  }
804 
809  public void updateName() {
810  setName(String.format("%s (%d)", Account.Type.CREDIT_CARD.getDisplayName(), accountTypeResults.getCount(Account.Type.CREDIT_CARD.getTypeName())));
811  }
812 
813  @Subscribe
814  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
815  updateName();
816  }
817 
818  @Subscribe
819  void handleDataAdded(ModuleDataEvent event) {
820  updateName();
821  }
822 
823  @Override
824  public boolean isLeafTypeNode() {
825  return false;
826  }
827 
828  @Override
829  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
830  return visitor.visit(this);
831  }
832 
833  @Override
834  public String getItemType() {
835  return getClass().getName();
836  }
837  }
838 
839  final private class FileWithCCNFactory extends ObservingChildren<FileWithCCN> {
840 
841  private final PropertyChangeListener pcl = new PropertyChangeListener() {
842  @Override
843  public void propertyChange(PropertyChangeEvent evt) {
844  String eventType = evt.getPropertyName();
845  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
852  try {
860  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
861  if (null != eventData
862  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
863  reviewStatusBus.post(eventData);
864  }
865  } catch (NoCurrentCaseException notUsed) {
866  // Case is closed, do nothing.
867  }
868  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
869  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
876  try {
878  refresh(true);
879 
880  } catch (NoCurrentCaseException notUsed) {
881  // Case is closed, do nothing.
882  }
883  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
884  // case was closed. Remove listeners so that we don't get called with a stale case handle
885  if (evt.getNewValue() == null) {
886  removeNotify();
887  skCase = null;
888  }
889  }
890  }
891  };
892 
893  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
894 
895  @Override
896  protected void addNotify() {
900  super.addNotify();
901  }
902 
903  @Override
904  protected void finalize() throws Throwable {
905  super.finalize();
909  }
910 
911  @Subscribe
912  @Override
913  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
914  refresh(true);
915  }
916 
917  @Subscribe
918  @Override
919  void handleDataAdded(ModuleDataEvent event) {
920  refresh(true);
921  }
922 
923  @Override
924  protected boolean createKeys(List<FileWithCCN> list) {
925  String query
926  = "SELECT blackboard_artifacts.obj_id," //NON-NLS
927  + " solr_attribute.value_text AS solr_document_id, "; //NON-NLS
928  if (skCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
929  query += " string_agg(blackboard_artifacts.artifact_id::character varying, ',') AS artifact_IDs, " //NON-NLS
930  + " string_agg(blackboard_artifacts.review_status_id::character varying, ',') AS review_status_ids, ";
931  } else {
932  query += " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS
933  + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids, ";
934  }
935  query += " COUNT( blackboard_artifacts.artifact_id) AS hits " //NON-NLS
936  + " FROM blackboard_artifacts " //NON-NLS
937  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
938  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
939  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
940  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
941  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
942  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
945  + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS
946  + " ORDER BY hits DESC "; //NON-NLS
947  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
948  ResultSet resultSet = results.getResultSet();) {
949  while (resultSet.next()) {
950  list.add(new FileWithCCN(
951  resultSet.getLong("obj_id"), //NON-NLS
952  resultSet.getString("solr_document_id"), //NON-NLS
953  unGroupConcat(resultSet.getString("artifact_IDs"), Long::valueOf), //NON-NLS
954  resultSet.getLong("hits"), //NON-NLS
955  new HashSet<>(unGroupConcat(resultSet.getString("review_status_ids"), reviewStatusID -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(reviewStatusID)))))); //NON-NLS
956  }
957  } catch (TskCoreException | SQLException ex) {
958  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
959 
960  }
961  return true;
962  }
963 
964  @Override
965  protected Node[] createNodesForKey(FileWithCCN key) {
966  //add all account artifacts for the file and the file itself to the lookup
967  try {
968  List<Object> lookupContents = new ArrayList<>();
969  for (long artId : key.artifactIDs) {
970  lookupContents.add(skCase.getBlackboardArtifact(artId));
971  }
972  AbstractFile abstractFileById = skCase.getAbstractFileById(key.getObjID());
973  lookupContents.add(abstractFileById);
974  return new Node[]{new FileWithCCNNode(key, abstractFileById, lookupContents.toArray())};
975  } catch (TskCoreException ex) {
976  LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS
977  return new Node[0];
978  }
979  }
980  }
981 
986  final public class ByFileNode extends DisplayableItemNode {
987 
991  private ByFileNode() {
992  super(Children.create(new FileWithCCNFactory(), true), Lookups.singleton("By File"));
993  setName("By File"); //NON-NLS
994  updateDisplayName();
995  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS
996  reviewStatusBus.register(this);
997  }
998 
999  @NbBundle.Messages({
1000  "# {0} - number of children",
1001  "Accounts.ByFileNode.displayName=By File ({0})"})
1002  private void updateDisplayName() {
1003  String query
1004  = "SELECT count(*) FROM ( SELECT count(*) AS documents "
1005  + " FROM blackboard_artifacts " //NON-NLS
1006  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
1007  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
1008  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
1009  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
1010  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
1011  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1014  + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo";
1015  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1016  ResultSet resultSet = results.getResultSet();) {
1017  while (resultSet.next()) {
1018  if (skCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
1019  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count")));
1020  } else {
1021  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count(*)")));
1022  }
1023  }
1024  } catch (TskCoreException | SQLException ex) {
1025  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
1026 
1027  }
1028  }
1029 
1030  @Override
1031  public boolean isLeafTypeNode() {
1032  return true;
1033  }
1034 
1035  @Override
1036  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1037  return visitor.visit(this);
1038  }
1039 
1040  @Override
1041  public String getItemType() {
1042  return getClass().getName();
1043  }
1044 
1045  @Subscribe
1046  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1047  updateDisplayName();
1048  }
1049 
1050  @Subscribe
1051  void handleDataAdded(ModuleDataEvent event) {
1052  updateDisplayName();
1053  }
1054  }
1055 
1056  final private class BINFactory extends ObservingChildren<BinResult> {
1057 
1058  private final PropertyChangeListener pcl = new PropertyChangeListener() {
1059  @Override
1060  public void propertyChange(PropertyChangeEvent evt) {
1061  String eventType = evt.getPropertyName();
1062  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
1069  try {
1077  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
1078  if (null != eventData
1079  && eventData.getBlackboardArtifactType().getTypeID() == Type.TSK_ACCOUNT.getTypeID()) {
1080  reviewStatusBus.post(eventData);
1081  }
1082  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
1083  // Case is closed, do nothing.
1084  }
1085  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
1086  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
1093  try {
1095 
1096  refresh(true);
1097  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
1098  // Case is closed, do nothing.
1099  }
1100  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())
1101  && (evt.getNewValue() == null)) {
1102  // case was closed. Remove listeners so that we don't get called with a stale case handle
1103  removeNotify();
1104  skCase = null;
1105  }
1106  }
1107  };
1108 
1109  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
1110 
1111  @Override
1112  protected void addNotify() {
1116  super.addNotify();
1117  }
1118 
1119  @Override
1120  protected void finalize() throws Throwable{
1121  super.finalize();
1125  }
1126 
1127  @Subscribe
1128  @Override
1129  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1130  refresh(true);
1131  }
1132 
1133  @Subscribe
1134  @Override
1135  void handleDataAdded(ModuleDataEvent event) {
1136  refresh(true);
1137  }
1138 
1139  @Override
1140  protected boolean createKeys(List<BinResult> list) {
1141 
1142  RangeMap<Integer, BinResult> binRanges = TreeRangeMap.create();
1143 
1144  String query
1145  = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS
1146  + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS
1147  + " FROM blackboard_artifacts " //NON-NLS
1148  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
1149  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1150  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1153  + " GROUP BY BIN " //NON-NLS
1154  + " ORDER BY BIN "; //NON-NLS
1155  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1156  ResultSet resultSet = results.getResultSet();) {
1157  //sort all te individual bins in to the ranges
1158  while (resultSet.next()) {
1159  final Integer bin = Integer.valueOf(resultSet.getString("BIN"));
1160  long count = resultSet.getLong("count");
1161 
1162  BINRange binRange = (BINRange) CreditCards.getBINInfo(bin);
1163  BinResult previousResult = binRanges.get(bin);
1164 
1165  if (previousResult != null) {
1166  binRanges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd()));
1167  count += previousResult.getCount();
1168  }
1169 
1170  if (binRange == null) {
1171  binRanges.put(Range.closed(bin, bin), new BinResult(count, bin, bin));
1172  } else {
1173  binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange));
1174  }
1175  }
1176  binRanges.asMapOfRanges().values().forEach(list::add);
1177  } catch (TskCoreException | SQLException ex) {
1178  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1179  }
1180 
1181  return true;
1182  }
1183 
1184  @Override
1185  protected Node[] createNodesForKey(BinResult key) {
1186  return new Node[]{new BINNode(key)};
1187  }
1188  }
1189 
1194  final public class ByBINNode extends DisplayableItemNode {
1195 
1199  @NbBundle.Messages("Accounts.ByBINNode.name=By BIN")
1200  private ByBINNode() {
1201  super(Children.create(new BINFactory(), true), Lookups.singleton(Bundle.Accounts_ByBINNode_name()));
1202  setName(Bundle.Accounts_ByBINNode_name()); //NON-NLS
1203  updateDisplayName();
1204  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1205  reviewStatusBus.register(this);
1206  }
1207 
1208  @NbBundle.Messages({
1209  "# {0} - number of children",
1210  "Accounts.ByBINNode.displayName=By BIN ({0})"})
1211  private void updateDisplayName() {
1212  String query
1213  = "SELECT count(distinct SUBSTR(blackboard_attributes.value_text,1,8)) AS BINs " //NON-NLS
1214  + " FROM blackboard_artifacts " //NON-NLS
1215  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
1216  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1217  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1219  + getRejectedArtifactFilterClause(); //NON-NLS
1220  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1221  ResultSet resultSet = results.getResultSet();) {
1222  while (resultSet.next()) {
1223  setDisplayName(Bundle.Accounts_ByBINNode_displayName(resultSet.getLong("BINs")));
1224  }
1225  } catch (TskCoreException | SQLException ex) {
1226  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1227  }
1228  }
1229 
1230  @Override
1231  public boolean isLeafTypeNode() {
1232  return false;
1233  }
1234 
1235  @Override
1236  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1237  return visitor.visit(this);
1238  }
1239 
1240  @Override
1241  public String getItemType() {
1242  return getClass().getName();
1243  }
1244 
1245  @Subscribe
1246  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1247  updateDisplayName();
1248  }
1249 
1250  @Subscribe
1251  void handleDataAdded(ModuleDataEvent event) {
1252  updateDisplayName();
1253  }
1254  }
1255 
1260  @Immutable
1261  final private static class FileWithCCN {
1262 
1263  @Override
1264  public int hashCode() {
1265  int hash = 5;
1266  hash = 79 * hash + (int) (this.objID ^ (this.objID >>> 32));
1267  hash = 79 * hash + Objects.hashCode(this.keywordSearchDocID);
1268  hash = 79 * hash + Objects.hashCode(this.artifactIDs);
1269  hash = 79 * hash + (int) (this.hits ^ (this.hits >>> 32));
1270  hash = 79 * hash + Objects.hashCode(this.statuses);
1271  return hash;
1272  }
1273 
1274  @Override
1275  public boolean equals(Object obj) {
1276  if (this == obj) {
1277  return true;
1278  }
1279  if (obj == null) {
1280  return false;
1281  }
1282  if (getClass() != obj.getClass()) {
1283  return false;
1284  }
1285  final FileWithCCN other = (FileWithCCN) obj;
1286  if (this.objID != other.objID) {
1287  return false;
1288  }
1289  if (this.hits != other.hits) {
1290  return false;
1291  }
1292  if (!Objects.equals(this.keywordSearchDocID, other.keywordSearchDocID)) {
1293  return false;
1294  }
1295  if (!Objects.equals(this.artifactIDs, other.artifactIDs)) {
1296  return false;
1297  }
1298  if (!Objects.equals(this.statuses, other.statuses)) {
1299  return false;
1300  }
1301  return true;
1302  }
1303 
1304  private final long objID;
1305  private final String keywordSearchDocID;
1306  private final List<Long> artifactIDs;
1307  private final long hits;
1308  private final Set<BlackboardArtifact.ReviewStatus> statuses;
1309 
1310  private FileWithCCN(long objID, String solrDocID, List<Long> artifactIDs, long hits, Set<BlackboardArtifact.ReviewStatus> statuses) {
1311  this.objID = objID;
1312  this.keywordSearchDocID = solrDocID;
1313  this.artifactIDs = artifactIDs;
1314  this.hits = hits;
1315  this.statuses = statuses;
1316  }
1317 
1323  public long getObjID() {
1324  return objID;
1325  }
1326 
1333  public String getkeywordSearchDocID() {
1334  return keywordSearchDocID;
1335  }
1336 
1342  public List<Long> getArtifactIDs() {
1343  return Collections.unmodifiableList(artifactIDs);
1344  }
1345 
1351  public long getHits() {
1352  return hits;
1353  }
1354 
1360  public Set<BlackboardArtifact.ReviewStatus> getStatuses() {
1361  return Collections.unmodifiableSet(statuses);
1362  }
1363  }
1364 
1381  static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
1382  return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
1383  : Stream.of(groupConcat.split(",")) //NON-NLS
1384  .map(mapper::apply)
1385  .collect(Collectors.toList());
1386  }
1387 
1391  final public class FileWithCCNNode extends DisplayableItemNode {
1392 
1393  private final FileWithCCN fileKey;
1394  private final String fileName;
1395 
1405  @NbBundle.Messages({
1406  "# {0} - raw file name",
1407  "# {1} - solr chunk id",
1408  "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"})
1409  private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) {
1410  super(Children.LEAF, Lookups.fixed(lookupContents));
1411  this.fileKey = key;
1412  this.fileName = (key.getkeywordSearchDocID() == null)
1413  ? content.getName()
1414  : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getkeywordSearchDocID(), "_")); //NON-NLS
1415  setName(fileName + key.getObjID());
1416  setDisplayName(fileName);
1417  }
1418 
1419  @Override
1420  public boolean isLeafTypeNode() {
1421  return true;
1422  }
1423 
1424  @Override
1425  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1426  return visitor.visit(this);
1427  }
1428 
1429  @Override
1430  public String getItemType() {
1431  return getClass().getName();
1432  }
1433 
1434  @Override
1435  @NbBundle.Messages({
1436  "Accounts.FileWithCCNNode.nameProperty.displayName=File",
1437  "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts",
1438  "Accounts.FileWithCCNNode.statusProperty.displayName=Status",
1439  "Accounts.FileWithCCNNode.noDescription=no description"})
1440  protected Sheet createSheet() {
1441  Sheet sheet = super.createSheet();
1442  Sheet.Set propSet = sheet.get(Sheet.PROPERTIES);
1443  if (propSet == null) {
1444  propSet = Sheet.createPropertiesSet();
1445  sheet.put(propSet);
1446  }
1447 
1448  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1449  Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1450  Bundle.Accounts_FileWithCCNNode_noDescription(),
1451  fileName));
1452  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1453  Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1454  Bundle.Accounts_FileWithCCNNode_noDescription(),
1455  fileKey.getHits()));
1456  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1457  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1458  Bundle.Accounts_FileWithCCNNode_noDescription(),
1459  fileKey.getStatuses().stream()
1460  .map(BlackboardArtifact.ReviewStatus::getDisplayName)
1461  .collect(Collectors.joining(", ")))); //NON-NLS
1462 
1463  return sheet;
1464  }
1465 
1466  @Override
1467  public Action[] getActions(boolean context) {
1468  Action[] actions = super.getActions(context);
1469  ArrayList<Action> arrayList = new ArrayList<>();
1470  arrayList.addAll(Arrays.asList(actions));
1471  try {
1472  arrayList.addAll(DataModelActionsFactory.getActions(Accounts.this.skCase.getContentById(fileKey.getObjID()), false));
1473  } catch (TskCoreException ex) {
1474  LOGGER.log(Level.SEVERE, "Error gettung content by id", ex);
1475  }
1476 
1477  arrayList.add(approveActionInstance);
1478  arrayList.add(rejectActionInstance);
1479 
1480  return arrayList.toArray(new Action[arrayList.size()]);
1481  }
1482  }
1483 
1484  final private class CreditCardNumberFactory extends ObservingChildren<DataArtifact> {
1485 
1486  private final BinResult bin;
1487 
1489  this.bin = bin;
1490  }
1491 
1492  @Subscribe
1493  @Override
1494  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1495  refresh(true);
1496  }
1497 
1498  @Subscribe
1499  @Override
1500  void handleDataAdded(ModuleDataEvent event) {
1501  refresh(true);
1502  }
1503 
1504  @Override
1505  protected boolean createKeys(List<DataArtifact> list) {
1506 
1507  String query
1508  = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS
1509  + " FROM blackboard_artifacts " //NON-NLS
1510  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1511  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1512  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1513  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1516  + " ORDER BY blackboard_attributes.value_text"; //NON-NLS
1517  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1518  ResultSet rs = results.getResultSet();) {
1519  while (rs.next()) {
1520  list.add(skCase.getBlackboard().getDataArtifactById(rs.getLong("artifact_obj_id"))); //NON-NLS
1521  }
1522  } catch (TskCoreException | SQLException ex) {
1523  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1524 
1525  }
1526  return true;
1527  }
1528 
1529  @Override
1530  protected Node[] createNodesForKey(DataArtifact artifact) {
1531  return new Node[]{new AccountArtifactNode(artifact)};
1532  }
1533  }
1534 
1535  private String getBinRangeString(BinResult bin) {
1536  if (bin.getBINStart() == bin.getBINEnd()) {
1537  return Integer.toString(bin.getBINStart());
1538  } else {
1539  return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + "");
1540  }
1541  }
1542 
1543  final public class BINNode extends DisplayableItemNode {
1544 
1548  private final BinResult bin;
1549 
1550  private BINNode(BinResult bin) {
1551  super(Children.create(new CreditCardNumberFactory(bin), true), Lookups.singleton(getBinRangeString(bin)));
1552  this.bin = bin;
1553  setName(getBinRangeString(bin));
1554  updateDisplayName();
1555  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1556  reviewStatusBus.register(this);
1557  }
1558 
1559  @Subscribe
1560  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1561  updateDisplayName();
1562  updateSheet();
1563  }
1564 
1565  @Subscribe
1566  void handleDataAdded(ModuleDataEvent event) {
1567  updateDisplayName();
1568  }
1569 
1570  private void updateDisplayName() {
1571  String query
1572  = "SELECT count(blackboard_artifacts.artifact_id ) AS count" //NON-NLS
1573  + " FROM blackboard_artifacts " //NON-NLS
1574  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1575  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.Type.TSK_ACCOUNT.getTypeID() //NON-NLS
1576  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1577  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1580  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1581  ResultSet resultSet = results.getResultSet();) {
1582  while (resultSet.next()) {
1583  setDisplayName(getBinRangeString(bin) + " (" + resultSet.getLong("count") + ")"); //NON-NLS
1584  }
1585  } catch (TskCoreException | SQLException ex) {
1586  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1587 
1588  }
1589 
1590  }
1591 
1592  @Override
1593  public boolean isLeafTypeNode() {
1594  return true;
1595  }
1596 
1597  @Override
1598  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1599  return visitor.visit(this);
1600  }
1601 
1602  @Override
1603  public String getItemType() {
1604  return getClass().getName();
1605  }
1606 
1607  private Sheet.Set getPropertySet(Sheet sheet) {
1608  Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
1609  if (sheetSet == null) {
1610  sheetSet = Sheet.createPropertiesSet();
1611  sheet.put(sheetSet);
1612  }
1613  return sheetSet;
1614  }
1615 
1616  @Override
1617  @NbBundle.Messages({
1618  "Accounts.BINNode.binProperty.displayName=Bank Identifier Number",
1619  "Accounts.BINNode.accountsProperty.displayName=Accounts",
1620  "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type",
1621  "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme",
1622  "Accounts.BINNode.brandProperty.displayName=Brand",
1623  "Accounts.BINNode.bankProperty.displayName=Bank",
1624  "Accounts.BINNode.bankCityProperty.displayName=Bank City",
1625  "Accounts.BINNode.bankCountryProperty.displayName=Bank Country",
1626  "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #",
1627  "Accounts.BINNode.bankURLProperty.displayName=Bank URL",
1628  "Accounts.BINNode.noDescription=no description"})
1629  protected Sheet createSheet() {
1630  Sheet sheet = super.createSheet();
1631  Sheet.Set properties = getPropertySet(sheet);
1632 
1633  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(),
1634  Bundle.Accounts_BINNode_binProperty_displayName(),
1635  Bundle.Accounts_BINNode_noDescription(),
1636  getBinRangeString(bin)));
1637  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(),
1638  Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1639  bin.getCount()));
1640 
1641  //add optional properties if they are available
1642  if (bin.hasDetails()) {
1643  bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(),
1644  Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1645  cardType)));
1646  bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(),
1647  Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1648  scheme)));
1649  bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(),
1650  Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1651  brand)));
1652  bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(),
1653  Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1654  bankName)));
1655  bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(),
1656  Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1657  bankCity)));
1658  bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(),
1659  Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1660  country)));
1661  bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(),
1662  Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1663  phoneNumber)));
1664  bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(),
1665  Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1666  url)));
1667  }
1668  return sheet;
1669  }
1670 
1671  private void updateSheet() {
1672  this.setSheet(createSheet());
1673  }
1674 
1675  }
1676 
1681  @Immutable
1682  final static private class BinResult implements CreditCards.BankIdentificationNumber {
1683 
1684  @Override
1685  public int hashCode() {
1686  int hash = 3;
1687  hash = 97 * hash + this.binEnd;
1688  hash = 97 * hash + this.binStart;
1689  return hash;
1690  }
1691 
1692  @Override
1693  public boolean equals(Object obj) {
1694  if (this == obj) {
1695  return true;
1696  }
1697  if (obj == null) {
1698  return false;
1699  }
1700  if (getClass() != obj.getClass()) {
1701  return false;
1702  }
1703  final BinResult other = (BinResult) obj;
1704  if (this.binEnd != other.binEnd) {
1705  return false;
1706  }
1707  if (this.binStart != other.binStart) {
1708  return false;
1709  }
1710  return true;
1711  }
1712 
1716  private final long count;
1717 
1718  private final BINRange binRange;
1719  private final int binEnd;
1720  private final int binStart;
1721 
1722  private BinResult(long count, @Nonnull BINRange binRange) {
1723  this.count = count;
1724  this.binRange = binRange;
1725  binStart = binRange.getBINstart();
1726  binEnd = binRange.getBINend();
1727  }
1728 
1729  private BinResult(long count, int start, int end) {
1730  this.count = count;
1731  this.binRange = null;
1732  binStart = start;
1733  binEnd = end;
1734  }
1735 
1736  int getBINStart() {
1737  return binStart;
1738  }
1739 
1740  int getBINEnd() {
1741  return binEnd;
1742  }
1743 
1744  long getCount() {
1745  return count;
1746  }
1747 
1748  boolean hasDetails() {
1749  return binRange != null;
1750  }
1751 
1752  @Override
1753  public Optional<Integer> getNumberLength() {
1754  return binRange.getNumberLength();
1755  }
1756 
1757  @Override
1758  public Optional<String> getBankCity() {
1759  return binRange.getBankCity();
1760  }
1761 
1762  @Override
1763  public Optional<String> getBankName() {
1764  return binRange.getBankName();
1765  }
1766 
1767  @Override
1768  public Optional<String> getBankPhoneNumber() {
1769  return binRange.getBankPhoneNumber();
1770  }
1771 
1772  @Override
1773  public Optional<String> getBankURL() {
1774  return binRange.getBankURL();
1775  }
1776 
1777  @Override
1778  public Optional<String> getBrand() {
1779  return binRange.getBrand();
1780  }
1781 
1782  @Override
1783  public Optional<String> getCardType() {
1784  return binRange.getCardType();
1785  }
1786 
1787  @Override
1788  public Optional<String> getCountry() {
1789  return binRange.getCountry();
1790  }
1791 
1792  @Override
1793  public Optional<String> getScheme() {
1794  return binRange.getScheme();
1795  }
1796  }
1797 
1798  final private class AccountArtifactNode extends BlackboardArtifactNode {
1799 
1800  private final BlackboardArtifact artifact;
1801 
1802  private AccountArtifactNode(BlackboardArtifact artifact) {
1803  super(artifact, "org/sleuthkit/autopsy/images/credit-card.png"); //NON-NLS
1804  this.artifact = artifact;
1805  setName(Long.toString(this.artifact.getArtifactID()));
1806 
1807  reviewStatusBus.register(this);
1808  }
1809 
1810  @Override
1811  public Action[] getActions(boolean context) {
1812  List<Action> actionsList = new ArrayList<>();
1813  actionsList.addAll(Arrays.asList(super.getActions(context)));
1814 
1815  actionsList.add(approveActionInstance);
1816  actionsList.add(rejectActionInstance);
1817 
1818  return actionsList.toArray(new Action[actionsList.size()]);
1819  }
1820 
1821  @Override
1822  protected Sheet createSheet() {
1823  Sheet sheet = super.createSheet();
1824  Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
1825  if (properties == null) {
1826  properties = Sheet.createPropertiesSet();
1827  sheet.put(properties);
1828  }
1829  properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1830  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1831  Bundle.Accounts_FileWithCCNNode_noDescription(),
1832  artifact.getReviewStatus().getDisplayName()));
1833 
1834  return sheet;
1835  }
1836 
1837  @Subscribe
1838  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1839 
1840  // Update the node if event includes this artifact
1841  event.artifacts.stream().filter((art) -> (art.getArtifactID() == this.artifact.getArtifactID())).map((_item) -> {
1842  return _item;
1843  }).forEachOrdered((_item) -> {
1844  updateSheet();
1845  });
1846  }
1847 
1848  private void updateSheet() {
1849  this.setSheet(createSheet());
1850  }
1851 
1852  }
1853 
1854  @Deprecated
1855  private final class ToggleShowRejected extends AbstractAction {
1856 
1857  @NbBundle.Messages("ToggleShowRejected.name=Show Rejected Results")
1858  ToggleShowRejected() {
1859  super(Bundle.ToggleShowRejected_name());
1860  }
1861 
1862  @Override
1863  public void actionPerformed(ActionEvent e) {
1864  showRejected = !showRejected;
1865  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1866  }
1867  }
1868 
1874  public void setShowRejected(boolean showRejected) {
1875  this.showRejected = showRejected;
1876  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1877  }
1878 
1879  private abstract class ReviewStatusAction extends AbstractAction {
1880 
1881  private final BlackboardArtifact.ReviewStatus newStatus;
1882 
1883  private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) {
1884  super(displayName);
1885  this.newStatus = newStatus;
1886 
1887  }
1888 
1889  @Override
1890  public void actionPerformed(ActionEvent e) {
1891 
1892  /*
1893  * get paths for selected nodes to reselect after applying review
1894  * status change
1895  */
1896  List<String[]> selectedPaths = Utilities.actionsGlobalContext().lookupAll(Node.class).stream()
1897  .map(node -> {
1898  String[] createPath;
1899  /*
1900  * If the we are rejecting and not showing rejected
1901  * results, then the selected node, won't exist any
1902  * more, so we select the previous one in stead.
1903  */
1904  if (newStatus == BlackboardArtifact.ReviewStatus.REJECTED && showRejected == false) {
1905  List<Node> siblings = Arrays.asList(node.getParentNode().getChildren().getNodes());
1906  if (siblings.size() > 1) {
1907  int indexOf = siblings.indexOf(node);
1908  //there is no previous for the first node, so instead we select the next one
1909  Node sibling = indexOf > 0
1910  ? siblings.get(indexOf - 1)
1911  : siblings.get(Integer.max(indexOf + 1, siblings.size() - 1));
1912  createPath = NodeOp.createPath(sibling, null);
1913  } else {
1914  /*
1915  * if there are no other siblings to select,
1916  * just return null, but note we need to filter
1917  * this out of stream below
1918  */
1919  return null;
1920  }
1921  } else {
1922  createPath = NodeOp.createPath(node, null);
1923  }
1924  //for the reselect to work we need to strip off the first part of the path.
1925  return Arrays.copyOfRange(createPath, 1, createPath.length);
1926  })
1927  .filter(Objects::nonNull)
1928  .collect(Collectors.toList());
1929 
1930  //change status of selected artifacts
1931  final Collection<? extends BlackboardArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
1932  artifacts.forEach(artifact -> {
1933  try {
1934  artifact.setReviewStatus(newStatus);
1935  } catch (TskCoreException ex) {
1936  LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS
1937  }
1938  });
1939  //post event
1940  reviewStatusBus.post(new ReviewStatusChangeEvent(artifacts, newStatus));
1941 
1942  final DataResultTopComponent directoryListing = DirectoryTreeTopComponent.findInstance().getDirectoryListing();
1943  final Node rootNode = directoryListing.getRootNode();
1944 
1945  //convert paths back to nodes
1946  List<Node> toArray = new ArrayList<>();
1947  selectedPaths.forEach(path -> {
1948  try {
1949  toArray.add(NodeOp.findPath(rootNode, path));
1950  } catch (NodeNotFoundException ex) { //NOPMD empty catch clause
1951  //just ingnore paths taht don't exist. this is expected since we are rejecting
1952  }
1953  });
1954  //select nodes
1955  directoryListing.setSelectedNodes(toArray.toArray(new Node[toArray.size()]));
1956  }
1957  }
1958 
1959  final private class ApproveAccounts extends ReviewStatusAction {
1960 
1961  @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"})
1962  private ApproveAccounts() {
1963  super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED);
1964  }
1965  }
1966 
1967  final private class RejectAccounts extends ReviewStatusAction {
1968 
1969  @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"})
1970  private RejectAccounts() {
1971  super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED);
1972  }
1973  }
1974 
1975  static private class ReviewStatusChangeEvent {
1976 
1977  Collection<? extends BlackboardArtifact> artifacts;
1978  BlackboardArtifact.ReviewStatus newReviewStatus;
1979 
1980  ReviewStatusChangeEvent(Collection<? extends BlackboardArtifact> artifacts, BlackboardArtifact.ReviewStatus newReviewStatus) {
1981  this.artifacts = artifacts;
1982  this.newReviewStatus = newReviewStatus;
1983  }
1984  }
1985 
1991  public static String getIconFilePath(Account.Type type) {
1992 
1993  if (type.equals(Account.Type.CREDIT_CARD)) {
1994  return ICON_BASE_PATH + "credit-card.png";
1995  } else if (type.equals(Account.Type.DEVICE)) {
1996  return ICON_BASE_PATH + "image.png";
1997  } else if (type.equals(Account.Type.EMAIL)) {
1998  return ICON_BASE_PATH + "email.png";
1999  } else if (type.equals(Account.Type.FACEBOOK)) {
2000  return ICON_BASE_PATH + "facebook.png";
2001  } else if (type.equals(Account.Type.INSTAGRAM)) {
2002  return ICON_BASE_PATH + "instagram.png";
2003  } else if (type.equals(Account.Type.MESSAGING_APP)) {
2004  return ICON_BASE_PATH + "messaging.png";
2005  } else if (type.equals(Account.Type.PHONE)) {
2006  return ICON_BASE_PATH + "phone.png";
2007  } else if (type.equals(Account.Type.TWITTER)) {
2008  return ICON_BASE_PATH + "twitter.png";
2009  } else if (type.equals(Account.Type.WEBSITE)) {
2010  return ICON_BASE_PATH + "web-file.png";
2011  } else if (type.equals(Account.Type.WHATSAPP)) {
2012  return ICON_BASE_PATH + "WhatsApp.png";
2013  } else {
2014  //there could be a default icon instead...
2015  return ICON_BASE_PATH + "face.png";
2016 // throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName());
2017  }
2018  }
2019 }
static final Set< IngestManager.IngestModuleEvent > INGEST_MODULE_EVENTS_OF_INTEREST
Definition: Accounts.java:99
boolean createKeys(List< CreditCardViewMode > list)
Definition: Accounts.java:771
BlackboardArtifact.Type getBlackboardArtifactType()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static synchronized IngestManager getInstance()
Set< BlackboardArtifact.ReviewStatus > getStatuses()
Definition: Accounts.java:1360
static List< Action > getActions(File file, boolean isArtifactSource)
static synchronized BankIdentificationNumber getBINInfo(int bin)
static String getIconFilePath(Account.Type type)
Definition: Accounts.java:1991
void removeIngestJobEventListener(final PropertyChangeListener listener)
final Set< BlackboardArtifact.ReviewStatus > statuses
Definition: Accounts.java:1308
void addIngestJobEventListener(final PropertyChangeListener listener)
BinResult(long count,@Nonnull BINRange binRange)
Definition: Accounts.java:1722
FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents)
Definition: Accounts.java:1409
FileWithCCN(long objID, String solrDocID, List< Long > artifactIDs, long hits, Set< BlackboardArtifact.ReviewStatus > statuses)
Definition: Accounts.java:1310
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
Accounts(SleuthkitCase skCase, long objId)
Definition: Accounts.java:136
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:98
ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus)
Definition: Accounts.java:1883
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:756

Copyright © 2012-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.