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

Copyright © 2012-2018 Basis Technology. Generated on: Wed Sep 18 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.