Autopsy  4.14.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-2019 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.HashSet;
37 import java.util.List;
38 import java.util.Objects;
39 import java.util.Optional;
40 import java.util.Set;
41 import java.util.function.Function;
42 import java.util.logging.Level;
43 import java.util.stream.Collectors;
44 import java.util.stream.Stream;
45 import javax.annotation.Nonnull;
46 import javax.annotation.concurrent.Immutable;
47 import javax.swing.AbstractAction;
48 import javax.swing.Action;
49 import org.apache.commons.lang3.StringUtils;
50 import org.openide.nodes.ChildFactory;
51 import org.openide.nodes.Children;
52 import org.openide.nodes.Node;
53 import org.openide.nodes.NodeNotFoundException;
54 import org.openide.nodes.NodeOp;
55 import org.openide.nodes.Sheet;
56 import org.openide.util.NbBundle;
57 import org.openide.util.Utilities;
58 import org.openide.util.lookup.Lookups;
74 import org.sleuthkit.datamodel.AbstractFile;
75 import org.sleuthkit.datamodel.Account;
76 import org.sleuthkit.datamodel.BlackboardArtifact;
77 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
78 import org.sleuthkit.datamodel.BlackboardAttribute;
79 import org.sleuthkit.datamodel.Content;
80 import org.sleuthkit.datamodel.SleuthkitCase;
81 import org.sleuthkit.datamodel.TskCoreException;
82 import org.sleuthkit.datamodel.TskData.DbType;
83 
88 final public class Accounts implements AutopsyVisitableItem {
89 
90  private static final Logger LOGGER = Logger.getLogger(Accounts.class.getName());
91  private static final String ICON_BASE_PATH = "/org/sleuthkit/autopsy/images/"; //NON-NLS
94 
95  @NbBundle.Messages("AccountsRootNode.name=Accounts")
96  final public static String NAME = Bundle.AccountsRootNode_name();
97 
98  private SleuthkitCase skCase;
99  private final long filteringDSObjId; // 0 if not filtering/grouping by data source
100 
101  private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus");
102 
103  /*
104  * Should rejected accounts be shown in the accounts section of the tree.
105  */
106  private boolean showRejected = false; //NOPMD redundant initializer
107 
110 
116  public Accounts(SleuthkitCase skCase) {
117  this(skCase, 0);
118  }
119 
126  public Accounts(SleuthkitCase skCase, long objId) {
127  this.skCase = skCase;
128  this.filteringDSObjId = objId;
129 
130  this.rejectActionInstance = new RejectAccounts();
131  this.approveActionInstance = new ApproveAccounts();
132  }
133 
134  @Override
135  public <T> T accept(AutopsyItemVisitor<T> visitor) {
136  return visitor.visit(this);
137  }
138 
147  return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS
148  }
149 
156  private String getFilterByDataSourceClause() {
157  if (filteringDSObjId > 0) {
158  return " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId + " ";
159  }
160 
161  return " ";
162  }
163 
171  @Deprecated
172  public Action newToggleShowRejectedAction() {
173  return new ToggleShowRejected();
174  }
175 
182  private abstract class ObservingChildren<X> extends ChildFactory.Detachable<X> {
183 
189  super();
190  }
191 
196  @Override
197  abstract protected boolean createKeys(List<X> list);
198 
204  @Subscribe
205  abstract void handleReviewStatusChange(ReviewStatusChangeEvent event);
206 
207  @Subscribe
208  abstract void handleDataAdded(ModuleDataEvent event);
209 
210  @Override
211  protected void removeNotify() {
212  super.removeNotify();
213  reviewStatusBus.unregister(ObservingChildren.this);
214  }
215 
216  @Override
217  protected void addNotify() {
218  super.addNotify();
219  refresh(true);
220  reviewStatusBus.register(ObservingChildren.this);
221  }
222  }
223 
227  @NbBundle.Messages({"Accounts.RootNode.displayName=Accounts"})
228  final public class AccountsRootNode extends DisplayableItemNode {
229 
230  public AccountsRootNode() {
231  super(Children.create(new AccountTypeFactory(), true), Lookups.singleton(Accounts.this));
232  setName(Accounts.NAME);
233  setDisplayName(Bundle.Accounts_RootNode_displayName());
234  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png"); //NON-NLS
235  }
236 
237  @Override
238  public boolean isLeafTypeNode() {
239  return false;
240  }
241 
242  @Override
243  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
244  return visitor.visit(this);
245  }
246 
247  @Override
248  public String getItemType() {
249  return getClass().getName();
250  }
251  }
252 
256  private class AccountTypeFactory extends ObservingChildren<String> {
257 
258  /*
259  * The pcl is in this class because it has the easiest mechanisms to add
260  * and remove itself during its life cycles.
261  */
262  private final PropertyChangeListener pcl = new PropertyChangeListener() {
263  @Override
264  public void propertyChange(PropertyChangeEvent evt) {
265  String eventType = evt.getPropertyName();
266  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
273  try {
281  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
282  if (null != eventData
283  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
284  reviewStatusBus.post(eventData);
285  }
286  } catch (NoCurrentCaseException notUsed) {
287  // Case is closed, do nothing.
288  }
289  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
290  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
297  try {
299  refresh(true);
300  } catch (NoCurrentCaseException notUsed) {
301  // Case is closed, do nothing.
302  }
303  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
304  // case was closed. Remove listeners so that we don't get called with a stale case handle
305  if (evt.getNewValue() == null) {
306  removeNotify();
307  skCase = null;
308  }
309  }
310  }
311  };
312 
313  @Subscribe
314  @Override
315  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
316  refresh(true);
317  }
318 
319  @Subscribe
320  @Override
321  void handleDataAdded(ModuleDataEvent event) {
322  refresh(true);
323  }
324 
325  @Override
326  protected boolean createKeys(List<String> list) {
327  String accountTypesInUseQuery
328  = "SELECT DISTINCT blackboard_attributes.value_text as account_type "
329  + " FROM blackboard_artifacts " //NON-NLS
330  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
331  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
332  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
334 
335  try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery);
336  ResultSet resultSet = executeQuery.getResultSet()) {
337  while (resultSet.next()) {
338  String accountType = resultSet.getString("account_type");
339  list.add(accountType);
340  }
341  } catch (TskCoreException | SQLException ex) {
342  LOGGER.log(Level.SEVERE, "Error querying for account_types", ex);
343  }
344 
345  return true;
346  }
347 
348  @Override
349  protected Node[] createNodesForKey(String acountTypeName) {
350 
351  if (Account.Type.CREDIT_CARD.getTypeName().equals(acountTypeName)) {
352  return new Node[]{new CreditCardNumberAccountTypeNode()};
353  } else {
354 
355  try {
356  Account.Type accountType = skCase.getCommunicationsManager().getAccountType(acountTypeName);
357  return new Node[]{new DefaultAccountTypeNode(accountType)};
358  } catch (TskCoreException ex) {
359  LOGGER.log(Level.SEVERE, "Error getting display name for account type. ", ex);
360  }
361 
362  return new Node[]{};
363  }
364  }
365 
366  @Override
367  protected void removeNotify() {
371  super.removeNotify();
372  }
373 
374  @Override
375  protected void addNotify() {
379  super.addNotify();
380  refresh(true);
381  }
382 
383  }
384 
385  final private class DefaultAccountFactory extends ObservingChildren<Long> {
386 
387  private final Account.Type accountType;
388 
389  private DefaultAccountFactory(Account.Type accountType) {
390  this.accountType = accountType;
391  }
392 
393  private final PropertyChangeListener pcl = new PropertyChangeListener() {
394  @Override
395  public void propertyChange(PropertyChangeEvent evt) {
396  String eventType = evt.getPropertyName();
397  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
404  try {
412  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
413  if (null != eventData
414  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
415  reviewStatusBus.post(eventData);
416  }
417  } catch (NoCurrentCaseException notUsed) {
418  // Case is closed, do nothing.
419  }
420  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
421  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
428  try {
430  refresh(true);
431 
432  } catch (NoCurrentCaseException notUsed) {
433  // Case is closed, do nothing.
434  }
435  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
436  // case was closed. Remove listeners so that we don't get called with a stale case handle
437  if (evt.getNewValue() == null) {
438  removeNotify();
439  skCase = null;
440  }
441  }
442  }
443  };
444 
445  @Override
446  protected void addNotify() {
450  super.addNotify();
451  }
452 
453  @Override
454  protected void removeNotify() {
458  super.removeNotify();
459  }
460 
461  @Override
462  protected boolean createKeys(List<Long> list) {
463  String query
464  = "SELECT blackboard_artifacts.artifact_id " //NON-NLS
465  + " FROM blackboard_artifacts " //NON-NLS
466  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
467  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
468  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
469  + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS
471  + getRejectedArtifactFilterClause(); //NON-NLS
472  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
473  ResultSet rs = results.getResultSet();) {
474  while (rs.next()) {
475  list.add(rs.getLong("artifact_id")); //NON-NLS
476  }
477  } catch (TskCoreException | SQLException ex) {
478  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
479  }
480 
481  return true;
482  }
483 
484  @Override
485  protected Node[] createNodesForKey(Long t) {
486  try {
487  return new Node[]{new BlackboardArtifactNode(skCase.getBlackboardArtifact(t))};
488  } catch (TskCoreException ex) {
489  LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex);
490  return new Node[0];
491  }
492  }
493 
494  @Subscribe
495  @Override
496  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
497  refresh(true);
498  }
499 
500  @Subscribe
501  @Override
502  void handleDataAdded(ModuleDataEvent event) {
503  refresh(true);
504  }
505  }
506 
511  final public class DefaultAccountTypeNode extends DisplayableItemNode {
512 
513  private DefaultAccountTypeNode(Account.Type accountType) {
514  super(Children.create(new DefaultAccountFactory(accountType), true), Lookups.singleton(accountType));
515  setName(accountType.getDisplayName());
516  this.setIconBaseWithExtension(getIconFilePath(accountType)); //NON-NLS
517  }
518 
519  @Override
520  public boolean isLeafTypeNode() {
521  return true;
522  }
523 
524  @Override
525  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
526  return visitor.visit(this);
527  }
528 
529  @Override
530  public String getItemType() {
531  return getClass().getName();
532  }
533  }
534 
538  private enum CreditCardViewMode {
541  }
542 
543  final private class ViewModeFactory extends ObservingChildren<CreditCardViewMode> {
544 
545  private final PropertyChangeListener pcl = new PropertyChangeListener() {
546  @Override
547  public void propertyChange(PropertyChangeEvent evt) {
548  String eventType = evt.getPropertyName();
549  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
556  try {
564  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
565  if (null != eventData
566  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
567  reviewStatusBus.post(eventData);
568  }
569  } catch (NoCurrentCaseException notUsed) {
570  // Case is closed, do nothing.
571  }
572  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
573  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
580  try {
582  refresh(true);
583 
584  } catch (NoCurrentCaseException notUsed) {
585  // Case is closed, do nothing.
586  }
587  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
588  // case was closed. Remove listeners so that we don't get called with a stale case handle
589  if (evt.getNewValue() == null) {
590  removeNotify();
591  skCase = null;
592  }
593  }
594  }
595  };
596 
597  @Subscribe
598  @Override
599  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
600  refresh(true);
601  }
602 
603  @Subscribe
604  @Override
605  void handleDataAdded(ModuleDataEvent event) {
606  refresh(true);
607  }
608 
609  @Override
610  protected void addNotify() {
614  super.addNotify();
615  }
616 
617  @Override
618  protected void removeNotify() {
622  super.removeNotify();
623  }
624 
628  @Override
629  protected boolean createKeys(List<CreditCardViewMode> list) {
630  list.addAll(Arrays.asList(CreditCardViewMode.values()));
631 
632  return true;
633  }
634 
635  @Override
636  protected Node[] createNodesForKey(CreditCardViewMode key) {
637  switch (key) {
638  case BY_BIN:
639  return new Node[]{new ByBINNode()};
640  case BY_FILE:
641  return new Node[]{new ByFileNode()};
642  default:
643  return new Node[0];
644  }
645  }
646  }
647 
652 
658  super(Children.create(new ViewModeFactory(), true), Lookups.singleton(Account.Type.CREDIT_CARD.getDisplayName()));
659  setName(Account.Type.CREDIT_CARD.getDisplayName());
660  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS
661  }
662 
663  @Override
664  public boolean isLeafTypeNode() {
665  return false;
666  }
667 
668  @Override
669  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
670  return visitor.visit(this);
671  }
672 
673  @Override
674  public String getItemType() {
675  return getClass().getName();
676  }
677  }
678 
679  final private class FileWithCCNFactory extends ObservingChildren<FileWithCCN> {
680 
681  private final PropertyChangeListener pcl = new PropertyChangeListener() {
682  @Override
683  public void propertyChange(PropertyChangeEvent evt) {
684  String eventType = evt.getPropertyName();
685  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
692  try {
700  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
701  if (null != eventData
702  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
703  reviewStatusBus.post(eventData);
704  }
705  } catch (NoCurrentCaseException notUsed) {
706  // Case is closed, do nothing.
707  }
708  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
709  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
716  try {
718  refresh(true);
719 
720  } catch (NoCurrentCaseException notUsed) {
721  // Case is closed, do nothing.
722  }
723  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
724  // case was closed. Remove listeners so that we don't get called with a stale case handle
725  if (evt.getNewValue() == null) {
726  removeNotify();
727  skCase = null;
728  }
729  }
730  }
731  };
732 
733  @Override
734  protected void addNotify() {
738  super.addNotify();
739  }
740 
741  @Override
742  protected void removeNotify() {
746  super.removeNotify();
747  }
748 
749  @Subscribe
750  @Override
751  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
752  refresh(true);
753  }
754 
755  @Subscribe
756  @Override
757  void handleDataAdded(ModuleDataEvent event) {
758  refresh(true);
759  }
760 
761  @Override
762  protected boolean createKeys(List<FileWithCCN> list) {
763  String query
764  = "SELECT blackboard_artifacts.obj_id," //NON-NLS
765  + " solr_attribute.value_text AS solr_document_id, "; //NON-NLS
766  if (skCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
767  query += " string_agg(blackboard_artifacts.artifact_id::character varying, ',') AS artifact_IDs, " //NON-NLS
768  + " string_agg(blackboard_artifacts.review_status_id::character varying, ',') AS review_status_ids, ";
769  } else {
770  query += " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS
771  + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids, ";
772  }
773  query += " COUNT( blackboard_artifacts.artifact_id) AS hits " //NON-NLS
774  + " FROM blackboard_artifacts " //NON-NLS
775  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
776  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
777  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
778  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
779  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
780  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
783  + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS
784  + " ORDER BY hits DESC "; //NON-NLS
785  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
786  ResultSet resultSet = results.getResultSet();) {
787  while (resultSet.next()) {
788  list.add(new FileWithCCN(
789  resultSet.getLong("obj_id"), //NON-NLS
790  resultSet.getString("solr_document_id"), //NON-NLS
791  unGroupConcat(resultSet.getString("artifact_IDs"), Long::valueOf), //NON-NLS
792  resultSet.getLong("hits"), //NON-NLS
793  new HashSet<>(unGroupConcat(resultSet.getString("review_status_ids"), reviewStatusID -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(reviewStatusID)))))); //NON-NLS
794  }
795  } catch (TskCoreException | SQLException ex) {
796  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
797 
798  }
799  return true;
800  }
801 
802  @Override
803  protected Node[] createNodesForKey(FileWithCCN key) {
804  //add all account artifacts for the file and the file itself to the lookup
805  try {
806  List<Object> lookupContents = new ArrayList<>();
807  for (long artId : key.artifactIDs) {
808  lookupContents.add(skCase.getBlackboardArtifact(artId));
809  }
810  AbstractFile abstractFileById = skCase.getAbstractFileById(key.getObjID());
811  lookupContents.add(abstractFileById);
812  return new Node[]{new FileWithCCNNode(key, abstractFileById, lookupContents.toArray())};
813  } catch (TskCoreException ex) {
814  LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS
815  return new Node[0];
816  }
817  }
818  }
819 
824  final public class ByFileNode extends DisplayableItemNode {
825 
829  private ByFileNode() {
830  super(Children.create(new FileWithCCNFactory(), true), Lookups.singleton("By File"));
831  setName("By File"); //NON-NLS
832  updateDisplayName();
833  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS
834  reviewStatusBus.register(this);
835  }
836 
837  @NbBundle.Messages({
838  "# {0} - number of children",
839  "Accounts.ByFileNode.displayName=By File ({0})"})
840  private void updateDisplayName() {
841  String query
842  = "SELECT count(*) FROM ( SELECT count(*) AS documents "
843  + " FROM blackboard_artifacts " //NON-NLS
844  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
845  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
846  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
847  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
848  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
849  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
852  + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo";
853  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
854  ResultSet resultSet = results.getResultSet();) {
855  while (resultSet.next()) {
856  if (skCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
857  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count")));
858  } else {
859  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count(*)")));
860  }
861  }
862  } catch (TskCoreException | SQLException ex) {
863  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
864 
865  }
866  }
867 
868  @Override
869  public boolean isLeafTypeNode() {
870  return true;
871  }
872 
873  @Override
874  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
875  return visitor.visit(this);
876  }
877 
878  @Override
879  public String getItemType() {
880  return getClass().getName();
881  }
882 
883  @Subscribe
884  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
885  updateDisplayName();
886  }
887 
888  @Subscribe
889  void handleDataAdded(ModuleDataEvent event) {
890  updateDisplayName();
891  }
892  }
893 
894  final private class BINFactory extends ObservingChildren<BinResult> {
895 
896  private final PropertyChangeListener pcl = new PropertyChangeListener() {
897  @Override
898  public void propertyChange(PropertyChangeEvent evt) {
899  String eventType = evt.getPropertyName();
900  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
907  try {
915  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
916  if (null != eventData
917  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
918  reviewStatusBus.post(eventData);
919  }
920  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
921  // Case is closed, do nothing.
922  }
923  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
924  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
931  try {
933 
934  refresh(true);
935  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
936  // Case is closed, do nothing.
937  }
938  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())
939  && (evt.getNewValue() == null)) {
940  // case was closed. Remove listeners so that we don't get called with a stale case handle
941  removeNotify();
942  skCase = null;
943  }
944  }
945  };
946 
947  @Override
948  protected void addNotify() {
952  super.addNotify();
953  }
954 
955  @Override
956  protected void removeNotify() {
960  super.removeNotify();
961  }
962 
963  @Subscribe
964  @Override
965  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
966  refresh(true);
967  }
968 
969  @Subscribe
970  @Override
971  void handleDataAdded(ModuleDataEvent event) {
972  refresh(true);
973  }
974 
975  @Override
976  protected boolean createKeys(List<BinResult> list) {
977 
978  RangeMap<Integer, BinResult> binRanges = TreeRangeMap.create();
979 
980  String query
981  = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS
982  + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS
983  + " FROM blackboard_artifacts " //NON-NLS
984  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
985  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
986  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
989  + " GROUP BY BIN " //NON-NLS
990  + " ORDER BY BIN "; //NON-NLS
991  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
992  ResultSet resultSet = results.getResultSet();) {
993  //sort all te individual bins in to the ranges
994  while (resultSet.next()) {
995  final Integer bin = Integer.valueOf(resultSet.getString("BIN"));
996  long count = resultSet.getLong("count");
997 
998  BINRange binRange = (BINRange) CreditCards.getBINInfo(bin);
999  BinResult previousResult = binRanges.get(bin);
1000 
1001  if (previousResult != null) {
1002  binRanges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd()));
1003  count += previousResult.getCount();
1004  }
1005 
1006  if (binRange == null) {
1007  binRanges.put(Range.closed(bin, bin), new BinResult(count, bin, bin));
1008  } else {
1009  binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange));
1010  }
1011  }
1012  binRanges.asMapOfRanges().values().forEach(list::add);
1013  } catch (TskCoreException | SQLException ex) {
1014  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1015  }
1016 
1017  return true;
1018  }
1019 
1020  @Override
1021  protected Node[] createNodesForKey(BinResult key) {
1022  return new Node[]{new BINNode(key)};
1023  }
1024  }
1025 
1030  final public class ByBINNode extends DisplayableItemNode {
1031 
1035  @NbBundle.Messages("Accounts.ByBINNode.name=By BIN")
1036  private ByBINNode() {
1037  super(Children.create(new BINFactory(), true), Lookups.singleton(Bundle.Accounts_ByBINNode_name()));
1038  setName(Bundle.Accounts_ByBINNode_name()); //NON-NLS
1039  updateDisplayName();
1040  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1041  reviewStatusBus.register(this);
1042  }
1043 
1044  @NbBundle.Messages({
1045  "# {0} - number of children",
1046  "Accounts.ByBINNode.displayName=By BIN ({0})"})
1047  private void updateDisplayName() {
1048  String query
1049  = "SELECT count(distinct SUBSTR(blackboard_attributes.value_text,1,8)) AS BINs " //NON-NLS
1050  + " FROM blackboard_artifacts " //NON-NLS
1051  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
1052  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
1053  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1055  + getRejectedArtifactFilterClause(); //NON-NLS
1056  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1057  ResultSet resultSet = results.getResultSet();) {
1058  while (resultSet.next()) {
1059  setDisplayName(Bundle.Accounts_ByBINNode_displayName(resultSet.getLong("BINs")));
1060  }
1061  } catch (TskCoreException | SQLException ex) {
1062  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1063  }
1064  }
1065 
1066  @Override
1067  public boolean isLeafTypeNode() {
1068  return false;
1069  }
1070 
1071  @Override
1072  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1073  return visitor.visit(this);
1074  }
1075 
1076  @Override
1077  public String getItemType() {
1078  return getClass().getName();
1079  }
1080 
1081  @Subscribe
1082  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1083  updateDisplayName();
1084  }
1085 
1086  @Subscribe
1087  void handleDataAdded(ModuleDataEvent event) {
1088  updateDisplayName();
1089  }
1090  }
1091 
1096  @Immutable
1097  final private static class FileWithCCN {
1098 
1099  @Override
1100  public int hashCode() {
1101  int hash = 5;
1102  hash = 79 * hash + (int) (this.objID ^ (this.objID >>> 32));
1103  hash = 79 * hash + Objects.hashCode(this.keywordSearchDocID);
1104  hash = 79 * hash + Objects.hashCode(this.artifactIDs);
1105  hash = 79 * hash + (int) (this.hits ^ (this.hits >>> 32));
1106  hash = 79 * hash + Objects.hashCode(this.statuses);
1107  return hash;
1108  }
1109 
1110  @Override
1111  public boolean equals(Object obj) {
1112  if (this == obj) {
1113  return true;
1114  }
1115  if (obj == null) {
1116  return false;
1117  }
1118  if (getClass() != obj.getClass()) {
1119  return false;
1120  }
1121  final FileWithCCN other = (FileWithCCN) obj;
1122  if (this.objID != other.objID) {
1123  return false;
1124  }
1125  if (this.hits != other.hits) {
1126  return false;
1127  }
1128  if (!Objects.equals(this.keywordSearchDocID, other.keywordSearchDocID)) {
1129  return false;
1130  }
1131  if (!Objects.equals(this.artifactIDs, other.artifactIDs)) {
1132  return false;
1133  }
1134  if (!Objects.equals(this.statuses, other.statuses)) {
1135  return false;
1136  }
1137  return true;
1138  }
1139 
1140  private final long objID;
1141  private final String keywordSearchDocID;
1142  private final List<Long> artifactIDs;
1143  private final long hits;
1144  private final Set<BlackboardArtifact.ReviewStatus> statuses;
1145 
1146  private FileWithCCN(long objID, String solrDocID, List<Long> artifactIDs, long hits, Set<BlackboardArtifact.ReviewStatus> statuses) {
1147  this.objID = objID;
1148  this.keywordSearchDocID = solrDocID;
1149  this.artifactIDs = artifactIDs;
1150  this.hits = hits;
1151  this.statuses = statuses;
1152  }
1153 
1159  public long getObjID() {
1160  return objID;
1161  }
1162 
1169  public String getkeywordSearchDocID() {
1170  return keywordSearchDocID;
1171  }
1172 
1178  public List<Long> getArtifactIDs() {
1179  return Collections.unmodifiableList(artifactIDs);
1180  }
1181 
1187  public long getHits() {
1188  return hits;
1189  }
1190 
1196  public Set<BlackboardArtifact.ReviewStatus> getStatuses() {
1197  return Collections.unmodifiableSet(statuses);
1198  }
1199  }
1200 
1217  static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
1218  return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
1219  : Stream.of(groupConcat.split(",")) //NON-NLS
1220  .map(mapper::apply)
1221  .collect(Collectors.toList());
1222  }
1223 
1227  final public class FileWithCCNNode extends DisplayableItemNode {
1228 
1229  private final FileWithCCN fileKey;
1230  private final String fileName;
1231 
1241  @NbBundle.Messages({
1242  "# {0} - raw file name",
1243  "# {1} - solr chunk id",
1244  "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"})
1245  private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) {
1246  super(Children.LEAF, Lookups.fixed(lookupContents));
1247  this.fileKey = key;
1248  this.fileName = (key.getkeywordSearchDocID() == null)
1249  ? content.getName()
1250  : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getkeywordSearchDocID(), "_")); //NON-NLS
1251  setName(fileName + key.getObjID());
1252  setDisplayName(fileName);
1253  }
1254 
1255  @Override
1256  public boolean isLeafTypeNode() {
1257  return true;
1258  }
1259 
1260  @Override
1261  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1262  return visitor.visit(this);
1263  }
1264 
1265  @Override
1266  public String getItemType() {
1267  return getClass().getName();
1268  }
1269 
1270  @Override
1271  @NbBundle.Messages({
1272  "Accounts.FileWithCCNNode.nameProperty.displayName=File",
1273  "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts",
1274  "Accounts.FileWithCCNNode.statusProperty.displayName=Status",
1275  "Accounts.FileWithCCNNode.noDescription=no description"})
1276  protected Sheet createSheet() {
1277  Sheet sheet = super.createSheet();
1278  Sheet.Set propSet = sheet.get(Sheet.PROPERTIES);
1279  if (propSet == null) {
1280  propSet = Sheet.createPropertiesSet();
1281  sheet.put(propSet);
1282  }
1283 
1284  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1285  Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1286  Bundle.Accounts_FileWithCCNNode_noDescription(),
1287  fileName));
1288  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1289  Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1290  Bundle.Accounts_FileWithCCNNode_noDescription(),
1291  fileKey.getHits()));
1292  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1293  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1294  Bundle.Accounts_FileWithCCNNode_noDescription(),
1295  fileKey.getStatuses().stream()
1296  .map(BlackboardArtifact.ReviewStatus::getDisplayName)
1297  .collect(Collectors.joining(", ")))); //NON-NLS
1298 
1299  return sheet;
1300  }
1301 
1302  @Override
1303  public Action[] getActions(boolean context) {
1304  Action[] actions = super.getActions(context);
1305  ArrayList<Action> arrayList = new ArrayList<>();
1306  arrayList.addAll(Arrays.asList(actions));
1307  try {
1308  arrayList.addAll(DataModelActionsFactory.getActions(Accounts.this.skCase.getContentById(fileKey.getObjID()), false));
1309  } catch (TskCoreException ex) {
1310  LOGGER.log(Level.SEVERE, "Error gettung content by id", ex);
1311  }
1312 
1313  arrayList.add(approveActionInstance);
1314  arrayList.add(rejectActionInstance);
1315 
1316  return arrayList.toArray(new Action[arrayList.size()]);
1317  }
1318  }
1319 
1320  final private class CreditCardNumberFactory extends ObservingChildren<Long> {
1321 
1322  private final BinResult bin;
1323 
1325  this.bin = bin;
1326  }
1327 
1328  @Subscribe
1329  @Override
1330  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1331  refresh(true);
1332  }
1333 
1334  @Subscribe
1335  @Override
1336  void handleDataAdded(ModuleDataEvent event) {
1337  refresh(true);
1338  }
1339 
1340  @Override
1341  protected boolean createKeys(List<Long> list) {
1342 
1343  String query
1344  = "SELECT blackboard_artifacts.artifact_id " //NON-NLS
1345  + " FROM blackboard_artifacts " //NON-NLS
1346  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1347  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
1348  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1349  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1352  + " ORDER BY blackboard_attributes.value_text"; //NON-NLS
1353  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1354  ResultSet rs = results.getResultSet();) {
1355  while (rs.next()) {
1356  list.add(rs.getLong("artifact_id")); //NON-NLS
1357  }
1358  } catch (TskCoreException | SQLException ex) {
1359  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1360 
1361  }
1362  return true;
1363  }
1364 
1365  @Override
1366  protected Node[] createNodesForKey(Long artifactID) {
1367  if (skCase == null) {
1368  return new Node[0];
1369  }
1370 
1371  try {
1372  BlackboardArtifact art = skCase.getBlackboardArtifact(artifactID);
1373  return new Node[]{new AccountArtifactNode(art)};
1374  } catch (TskCoreException ex) {
1375  LOGGER.log(Level.SEVERE, "Error creating BlackboardArtifactNode for artifact with ID " + artifactID, ex); //NON-NLS
1376  return new Node[0];
1377  }
1378  }
1379  }
1380 
1381  private String getBinRangeString(BinResult bin) {
1382  if (bin.getBINStart() == bin.getBINEnd()) {
1383  return Integer.toString(bin.getBINStart());
1384  } else {
1385  return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + "");
1386  }
1387  }
1388 
1389  final public class BINNode extends DisplayableItemNode {
1390 
1394  private final BinResult bin;
1395 
1396  private BINNode(BinResult bin) {
1397  super(Children.create(new CreditCardNumberFactory(bin), true), Lookups.singleton(getBinRangeString(bin)));
1398  this.bin = bin;
1399  setName(getBinRangeString(bin));
1400  updateDisplayName();
1401  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1402  reviewStatusBus.register(this);
1403  }
1404 
1405  @Subscribe
1406  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1407  updateDisplayName();
1408  updateSheet();
1409  }
1410 
1411  @Subscribe
1412  void handleDataAdded(ModuleDataEvent event) {
1413  updateDisplayName();
1414  }
1415 
1416  private void updateDisplayName() {
1417  String query
1418  = "SELECT count(blackboard_artifacts.artifact_id ) AS count" //NON-NLS
1419  + " FROM blackboard_artifacts " //NON-NLS
1420  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1421  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
1422  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1423  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1426  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1427  ResultSet resultSet = results.getResultSet();) {
1428  while (resultSet.next()) {
1429  setDisplayName(getBinRangeString(bin) + " (" + resultSet.getLong("count") + ")"); //NON-NLS
1430  }
1431  } catch (TskCoreException | SQLException ex) {
1432  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1433 
1434  }
1435 
1436  }
1437 
1438  @Override
1439  public boolean isLeafTypeNode() {
1440  return true;
1441  }
1442 
1443  @Override
1444  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1445  return visitor.visit(this);
1446  }
1447 
1448  @Override
1449  public String getItemType() {
1450  return getClass().getName();
1451  }
1452 
1453  private Sheet.Set getPropertySet(Sheet sheet) {
1454  Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
1455  if (sheetSet == null) {
1456  sheetSet = Sheet.createPropertiesSet();
1457  sheet.put(sheetSet);
1458  }
1459  return sheetSet;
1460  }
1461 
1462  @Override
1463  @NbBundle.Messages({
1464  "Accounts.BINNode.binProperty.displayName=Bank Identifier Number",
1465  "Accounts.BINNode.accountsProperty.displayName=Accounts",
1466  "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type",
1467  "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme",
1468  "Accounts.BINNode.brandProperty.displayName=Brand",
1469  "Accounts.BINNode.bankProperty.displayName=Bank",
1470  "Accounts.BINNode.bankCityProperty.displayName=Bank City",
1471  "Accounts.BINNode.bankCountryProperty.displayName=Bank Country",
1472  "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #",
1473  "Accounts.BINNode.bankURLProperty.displayName=Bank URL",
1474  "Accounts.BINNode.noDescription=no description"})
1475  protected Sheet createSheet() {
1476  Sheet sheet = super.createSheet();
1477  Sheet.Set properties = getPropertySet(sheet);
1478 
1479  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(),
1480  Bundle.Accounts_BINNode_binProperty_displayName(),
1481  Bundle.Accounts_BINNode_noDescription(),
1482  getBinRangeString(bin)));
1483  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(),
1484  Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1485  bin.getCount()));
1486 
1487  //add optional properties if they are available
1488  if (bin.hasDetails()) {
1489  bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(),
1490  Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1491  cardType)));
1492  bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(),
1493  Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1494  scheme)));
1495  bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(),
1496  Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1497  brand)));
1498  bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(),
1499  Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1500  bankName)));
1501  bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(),
1502  Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1503  bankCity)));
1504  bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(),
1505  Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1506  country)));
1507  bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(),
1508  Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1509  phoneNumber)));
1510  bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(),
1511  Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1512  url)));
1513  }
1514  return sheet;
1515  }
1516 
1517  private void updateSheet() {
1518  this.setSheet(createSheet());
1519  }
1520 
1521  }
1522 
1527  @Immutable
1528  final static private class BinResult implements CreditCards.BankIdentificationNumber {
1529 
1530  @Override
1531  public int hashCode() {
1532  int hash = 3;
1533  hash = 97 * hash + this.binEnd;
1534  hash = 97 * hash + this.binStart;
1535  return hash;
1536  }
1537 
1538  @Override
1539  public boolean equals(Object obj) {
1540  if (this == obj) {
1541  return true;
1542  }
1543  if (obj == null) {
1544  return false;
1545  }
1546  if (getClass() != obj.getClass()) {
1547  return false;
1548  }
1549  final BinResult other = (BinResult) obj;
1550  if (this.binEnd != other.binEnd) {
1551  return false;
1552  }
1553  if (this.binStart != other.binStart) {
1554  return false;
1555  }
1556  return true;
1557  }
1558 
1562  private final long count;
1563 
1564  private final BINRange binRange;
1565  private final int binEnd;
1566  private final int binStart;
1567 
1568  private BinResult(long count, @Nonnull BINRange binRange) {
1569  this.count = count;
1570  this.binRange = binRange;
1571  binStart = binRange.getBINstart();
1572  binEnd = binRange.getBINend();
1573  }
1574 
1575  private BinResult(long count, int start, int end) {
1576  this.count = count;
1577  this.binRange = null;
1578  binStart = start;
1579  binEnd = end;
1580  }
1581 
1582  int getBINStart() {
1583  return binStart;
1584  }
1585 
1586  int getBINEnd() {
1587  return binEnd;
1588  }
1589 
1590  long getCount() {
1591  return count;
1592  }
1593 
1594  boolean hasDetails() {
1595  return binRange != null;
1596  }
1597 
1598  @Override
1599  public Optional<Integer> getNumberLength() {
1600  return binRange.getNumberLength();
1601  }
1602 
1603  @Override
1604  public Optional<String> getBankCity() {
1605  return binRange.getBankCity();
1606  }
1607 
1608  @Override
1609  public Optional<String> getBankName() {
1610  return binRange.getBankName();
1611  }
1612 
1613  @Override
1614  public Optional<String> getBankPhoneNumber() {
1615  return binRange.getBankPhoneNumber();
1616  }
1617 
1618  @Override
1619  public Optional<String> getBankURL() {
1620  return binRange.getBankURL();
1621  }
1622 
1623  @Override
1624  public Optional<String> getBrand() {
1625  return binRange.getBrand();
1626  }
1627 
1628  @Override
1629  public Optional<String> getCardType() {
1630  return binRange.getCardType();
1631  }
1632 
1633  @Override
1634  public Optional<String> getCountry() {
1635  return binRange.getCountry();
1636  }
1637 
1638  @Override
1639  public Optional<String> getScheme() {
1640  return binRange.getScheme();
1641  }
1642  }
1643 
1644  final private class AccountArtifactNode extends BlackboardArtifactNode {
1645 
1646  private final BlackboardArtifact artifact;
1647 
1648  private AccountArtifactNode(BlackboardArtifact artifact) {
1649  super(artifact, "org/sleuthkit/autopsy/images/credit-card.png"); //NON-NLS
1650  this.artifact = artifact;
1651  setName(Long.toString(this.artifact.getArtifactID()));
1652 
1653  reviewStatusBus.register(this);
1654  }
1655 
1656  @Override
1657  public Action[] getActions(boolean context) {
1658  List<Action> actionsList = new ArrayList<>();
1659  actionsList.addAll(Arrays.asList(super.getActions(context)));
1660 
1661  actionsList.add(approveActionInstance);
1662  actionsList.add(rejectActionInstance);
1663 
1664  return actionsList.toArray(new Action[actionsList.size()]);
1665  }
1666 
1667  @Override
1668  protected Sheet createSheet() {
1669  Sheet sheet = super.createSheet();
1670  Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
1671  if (properties == null) {
1672  properties = Sheet.createPropertiesSet();
1673  sheet.put(properties);
1674  }
1675  properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1676  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1677  Bundle.Accounts_FileWithCCNNode_noDescription(),
1678  artifact.getReviewStatus().getDisplayName()));
1679 
1680  return sheet;
1681  }
1682 
1683  @Subscribe
1684  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1685 
1686  // Update the node if event includes this artifact
1687  event.artifacts.stream().filter((art) -> (art.getArtifactID() == this.artifact.getArtifactID())).map((_item) -> {
1688  return _item;
1689  }).forEachOrdered((_item) -> {
1690  updateSheet();
1691  });
1692  }
1693 
1694  private void updateSheet() {
1695  this.setSheet(createSheet());
1696  }
1697 
1698  }
1699 
1700  @Deprecated
1701  private final class ToggleShowRejected extends AbstractAction {
1702 
1703  @NbBundle.Messages("ToggleShowRejected.name=Show Rejected Results")
1704  ToggleShowRejected() {
1705  super(Bundle.ToggleShowRejected_name());
1706  }
1707 
1708  @Override
1709  public void actionPerformed(ActionEvent e) {
1710  showRejected = !showRejected;
1711  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1712  }
1713  }
1714 
1720  public void setShowRejected(boolean showRejected) {
1721  this.showRejected = showRejected;
1722  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1723  }
1724 
1725  private abstract class ReviewStatusAction extends AbstractAction {
1726 
1727  private final BlackboardArtifact.ReviewStatus newStatus;
1728 
1729  private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) {
1730  super(displayName);
1731  this.newStatus = newStatus;
1732 
1733  }
1734 
1735  @Override
1736  public void actionPerformed(ActionEvent e) {
1737 
1738  /*
1739  * get paths for selected nodes to reselect after applying review
1740  * status change
1741  */
1742  List<String[]> selectedPaths = Utilities.actionsGlobalContext().lookupAll(Node.class).stream()
1743  .map(node -> {
1744  String[] createPath;
1745  /*
1746  * If the we are rejecting and not showing rejected
1747  * results, then the selected node, won't exist any
1748  * more, so we select the previous one in stead.
1749  */
1750  if (newStatus == BlackboardArtifact.ReviewStatus.REJECTED && showRejected == false) {
1751  List<Node> siblings = Arrays.asList(node.getParentNode().getChildren().getNodes());
1752  if (siblings.size() > 1) {
1753  int indexOf = siblings.indexOf(node);
1754  //there is no previous for the first node, so instead we select the next one
1755  Node sibling = indexOf > 0
1756  ? siblings.get(indexOf - 1)
1757  : siblings.get(Integer.max(indexOf + 1, siblings.size() - 1));
1758  createPath = NodeOp.createPath(sibling, null);
1759  } else {
1760  /*
1761  * if there are no other siblings to select,
1762  * just return null, but note we need to filter
1763  * this out of stream below
1764  */
1765  return null;
1766  }
1767  } else {
1768  createPath = NodeOp.createPath(node, null);
1769  }
1770  //for the reselect to work we need to strip off the first part of the path.
1771  return Arrays.copyOfRange(createPath, 1, createPath.length);
1772  })
1773  .filter(Objects::nonNull)
1774  .collect(Collectors.toList());
1775 
1776  //change status of selected artifacts
1777  final Collection<? extends BlackboardArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
1778  artifacts.forEach(artifact -> {
1779  try {
1780  artifact.setReviewStatus(newStatus);
1781  } catch (TskCoreException ex) {
1782  LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS
1783  }
1784  });
1785  //post event
1786  reviewStatusBus.post(new ReviewStatusChangeEvent(artifacts, newStatus));
1787 
1788  final DataResultTopComponent directoryListing = DirectoryTreeTopComponent.findInstance().getDirectoryListing();
1789  final Node rootNode = directoryListing.getRootNode();
1790 
1791  //convert paths back to nodes
1792  List<Node> toArray = new ArrayList<>();
1793  selectedPaths.forEach(path -> {
1794  try {
1795  toArray.add(NodeOp.findPath(rootNode, path));
1796  } catch (NodeNotFoundException ex) { //NOPMD empty catch clause
1797  //just ingnore paths taht don't exist. this is expected since we are rejecting
1798  }
1799  });
1800  //select nodes
1801  directoryListing.setSelectedNodes(toArray.toArray(new Node[toArray.size()]));
1802  }
1803  }
1804 
1805  final private class ApproveAccounts extends ReviewStatusAction {
1806 
1807  @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"})
1808  private ApproveAccounts() {
1809  super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED);
1810  }
1811  }
1812 
1813  final private class RejectAccounts extends ReviewStatusAction {
1814 
1815  @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"})
1816  private RejectAccounts() {
1817  super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED);
1818  }
1819  }
1820 
1821  static private class ReviewStatusChangeEvent {
1822 
1823  Collection<? extends BlackboardArtifact> artifacts;
1824  BlackboardArtifact.ReviewStatus newReviewStatus;
1825 
1826  ReviewStatusChangeEvent(Collection<? extends BlackboardArtifact> artifacts, BlackboardArtifact.ReviewStatus newReviewStatus) {
1827  this.artifacts = artifacts;
1828  this.newReviewStatus = newReviewStatus;
1829  }
1830  }
1831 
1837  public static String getIconFilePath(Account.Type type) {
1838 
1839  if (type.equals(Account.Type.CREDIT_CARD)) {
1840  return ICON_BASE_PATH + "credit-card.png";
1841  } else if (type.equals(Account.Type.DEVICE)) {
1842  return ICON_BASE_PATH + "image.png";
1843  } else if (type.equals(Account.Type.EMAIL)) {
1844  return ICON_BASE_PATH + "email.png";
1845  } else if (type.equals(Account.Type.FACEBOOK)) {
1846  return ICON_BASE_PATH + "facebook.png";
1847  } else if (type.equals(Account.Type.INSTAGRAM)) {
1848  return ICON_BASE_PATH + "instagram.png";
1849  } else if (type.equals(Account.Type.MESSAGING_APP)) {
1850  return ICON_BASE_PATH + "messaging.png";
1851  } else if (type.equals(Account.Type.PHONE)) {
1852  return ICON_BASE_PATH + "phone.png";
1853  } else if (type.equals(Account.Type.TWITTER)) {
1854  return ICON_BASE_PATH + "twitter.png";
1855  } else if (type.equals(Account.Type.WEBSITE)) {
1856  return ICON_BASE_PATH + "web-file.png";
1857  } else if (type.equals(Account.Type.WHATSAPP)) {
1858  return ICON_BASE_PATH + "WhatsApp.png";
1859  } else {
1860  //there could be a default icon instead...
1861  return ICON_BASE_PATH + "face.png";
1862 // throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName());
1863  }
1864  }
1865 }
static final Set< IngestManager.IngestModuleEvent > INGEST_MODULE_EVENTS_OF_INTEREST
Definition: Accounts.java:93
boolean createKeys(List< CreditCardViewMode > list)
Definition: Accounts.java:629
BlackboardArtifact.Type getBlackboardArtifactType()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static synchronized IngestManager getInstance()
Set< BlackboardArtifact.ReviewStatus > getStatuses()
Definition: Accounts.java:1196
static List< Action > getActions(File file, boolean isArtifactSource)
static synchronized BankIdentificationNumber getBINInfo(int bin)
static String getIconFilePath(Account.Type type)
Definition: Accounts.java:1837
void removeIngestJobEventListener(final PropertyChangeListener listener)
final Set< BlackboardArtifact.ReviewStatus > statuses
Definition: Accounts.java:1144
void addIngestJobEventListener(final PropertyChangeListener listener)
BinResult(long count,@Nonnull BINRange binRange)
Definition: Accounts.java:1568
FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents)
Definition: Accounts.java:1245
FileWithCCN(long objID, String solrDocID, List< Long > artifactIDs, long hits, Set< BlackboardArtifact.ReviewStatus > statuses)
Definition: Accounts.java:1146
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
Accounts(SleuthkitCase skCase, long objId)
Definition: Accounts.java:126
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:486
static final Set< IngestManager.IngestJobEvent > INGEST_JOB_EVENTS_OF_INTEREST
Definition: Accounts.java:92
ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus)
Definition: Accounts.java:1729
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:531

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.