Autopsy  4.7.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-2018 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
92 
93  @NbBundle.Messages("AccountsRootNode.name=Accounts")
94  final public static String NAME = Bundle.AccountsRootNode_name();
95 
96  private SleuthkitCase skCase;
97  private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus");
98 
99  /* Should rejected accounts be shown in the accounts section of the tree. */
100  private boolean showRejected = false; //NOPMD redundant initializer
101 
104 
110  public Accounts(SleuthkitCase skCase) {
111  this.skCase = skCase;
112 
113  this.rejectActionInstance = new RejectAccounts();
114  this.approveActionInstance = new ApproveAccounts();
115  }
116 
117  @Override
118  public <T> T accept(AutopsyItemVisitor<T> visitor) {
119  return visitor.visit(this);
120  }
121 
130  return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS
131  }
132 
140  public Action newToggleShowRejectedAction() {
141  return new ToggleShowRejected();
142  }
143 
150  private abstract class ObservingChildren<X> extends ChildFactory.Detachable<X> {
151 
157  super();
158  }
159 
164  abstract protected boolean createKeys(List<X> list);
165 
171  @Subscribe
172  abstract void handleReviewStatusChange(ReviewStatusChangeEvent event);
173 
174  @Subscribe
175  abstract void handleDataAdded(ModuleDataEvent event);
176 
177  @Override
178  protected void removeNotify() {
179  super.removeNotify();
180  reviewStatusBus.unregister(ObservingChildren.this);
181  }
182 
183  @Override
184  protected void addNotify() {
185  super.addNotify();
186  refresh(true);
187  reviewStatusBus.register(ObservingChildren.this);
188  }
189  }
190 
194  @NbBundle.Messages({"Accounts.RootNode.displayName=Accounts"})
195  final public class AccountsRootNode extends DisplayableItemNode {
196 
197  public AccountsRootNode() {
198  super(Children.create(new AccountTypeFactory(), true), Lookups.singleton(Accounts.this));
199  setName(Accounts.NAME);
200  setDisplayName(Bundle.Accounts_RootNode_displayName());
201  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png"); //NON-NLS
202  }
203 
204  @Override
205  public boolean isLeafTypeNode() {
206  return false;
207  }
208 
209  @Override
210  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
211  return visitor.visit(this);
212  }
213 
214  @Override
215  public String getItemType() {
216  return getClass().getName();
217  }
218  }
219 
223  private class AccountTypeFactory extends ObservingChildren<String> {
224 
225  /*
226  * The pcl is in this class because it has the easiest mechanisms to add
227  * and remove itself during its life cycles.
228  */
229  private final PropertyChangeListener pcl = new PropertyChangeListener() {
230  @Override
231  public void propertyChange(PropertyChangeEvent evt) {
232  String eventType = evt.getPropertyName();
233  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
240  try {
248  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
249  if (null != eventData
250  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
251  reviewStatusBus.post(eventData);
252  }
253  } catch (NoCurrentCaseException notUsed) {
254  // Case is closed, do nothing.
255  }
256  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
257  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
264  try {
266  refresh(true);
267  } catch (NoCurrentCaseException notUsed) {
268  // Case is closed, do nothing.
269  }
270  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
271  // case was closed. Remove listeners so that we don't get called with a stale case handle
272  if (evt.getNewValue() == null) {
273  removeNotify();
274  skCase = null;
275  }
276  }
277  }
278  };
279 
280  @Subscribe
281  @Override
282  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
283  refresh(true);
284  }
285 
286  @Subscribe
287  @Override
288  void handleDataAdded(ModuleDataEvent event) {
289  refresh(true);
290  }
291 
292  @Override
293  protected boolean createKeys(List<String> list) {
294  try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(
295  "SELECT DISTINCT blackboard_attributes.value_text as account_type "
296  + " FROM blackboard_attributes "
297  + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID());
298  ResultSet resultSet = executeQuery.getResultSet()) {
299  while (resultSet.next()) {
300  String accountType = resultSet.getString("account_type");
301  list.add(accountType);
302  }
303  } catch (TskCoreException | SQLException ex) {
304  LOGGER.log(Level.SEVERE, "Error querying for account_types", ex);
305  }
306 
307  return true;
308  }
309 
310  @Override
311  protected Node[] createNodesForKey(String acountTypeName) {
312 
313  if (Account.Type.CREDIT_CARD.getTypeName().equals(acountTypeName)) {
314  return new Node[]{new CreditCardNumberAccountTypeNode()};
315  } else {
316 
317  try {
318  Account.Type accountType = skCase.getCommunicationsManager().getAccountType(acountTypeName);
319  return new Node[]{new DefaultAccountTypeNode(accountType)};
320  } catch (TskCoreException ex) {
321  LOGGER.log(Level.SEVERE, "Error getting display name for account type. ", ex);
322  }
323 
324  return new Node[]{};
325  }
326  }
327 
328  @Override
329  protected void removeNotify() {
333  super.removeNotify();
334  }
335 
336  @Override
337  protected void addNotify() {
341  super.addNotify();
342  refresh(true);
343  }
344 
345  }
346 
347  final private class DefaultAccountFactory extends ObservingChildren<Long> {
348 
349  private final Account.Type accountType;
350 
351  private DefaultAccountFactory(Account.Type accountType) {
352  this.accountType = accountType;
353  }
354 
355  private final PropertyChangeListener pcl = new PropertyChangeListener() {
356  @Override
357  public void propertyChange(PropertyChangeEvent evt) {
358  String eventType = evt.getPropertyName();
359  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
366  try {
374  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
375  if (null != eventData
376  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
377  reviewStatusBus.post(eventData);
378  }
379  } catch (NoCurrentCaseException notUsed) {
380  // Case is closed, do nothing.
381  }
382  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
383  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
390  try {
392  refresh(true);
393 
394  } catch (NoCurrentCaseException notUsed) {
395  // Case is closed, do nothing.
396  }
397  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
398  // case was closed. Remove listeners so that we don't get called with a stale case handle
399  if (evt.getNewValue() == null) {
400  removeNotify();
401  skCase = null;
402  }
403  }
404  }
405  };
406 
407  @Override
408  protected void addNotify() {
412  super.addNotify();
413  }
414 
415  @Override
416  protected void removeNotify() {
420  super.removeNotify();
421  }
422 
423  @Override
424  protected boolean createKeys(List<Long> list) {
425  String query =
426  "SELECT blackboard_artifacts.artifact_id " //NON-NLS
427  + " FROM blackboard_artifacts " //NON-NLS
428  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
429  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
430  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
431  + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS
432  + getRejectedArtifactFilterClause(); //NON-NLS
433  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
434  ResultSet rs = results.getResultSet();) {
435  while (rs.next()) {
436  list.add(rs.getLong("artifact_id")); //NON-NLS
437  }
438  } catch (TskCoreException | SQLException ex) {
439  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
440  }
441 
442  return true;
443  }
444 
445  @Override
446  protected Node[] createNodesForKey(Long t) {
447  try {
448  return new Node[]{new BlackboardArtifactNode(skCase.getBlackboardArtifact(t))};
449  } catch (TskCoreException ex) {
450  LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex);
451  return new Node[0];
452  }
453  }
454 
455  @Subscribe
456  @Override
457  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
458  refresh(true);
459  }
460 
461  @Subscribe
462  @Override
463  void handleDataAdded(ModuleDataEvent event) {
464  refresh(true);
465  }
466  }
467 
472  final public class DefaultAccountTypeNode extends DisplayableItemNode {
473 
474  private DefaultAccountTypeNode(Account.Type accountType) {
475  super(Children.create(new DefaultAccountFactory(accountType), true), Lookups.singleton(accountType));
476  setName(accountType.getDisplayName());
477  this.setIconBaseWithExtension(getIconFilePath(accountType)); //NON-NLS
478  }
479 
480  @Override
481  public boolean isLeafTypeNode() {
482  return true;
483  }
484 
485  @Override
486  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
487  return visitor.visit(this);
488  }
489 
490  @Override
491  public String getItemType() {
492  return getClass().getName();
493  }
494  }
495 
499  private enum CreditCardViewMode {
502  }
503 
504  final private class ViewModeFactory extends ObservingChildren<CreditCardViewMode> {
505 
506  private final PropertyChangeListener pcl = new PropertyChangeListener() {
507  @Override
508  public void propertyChange(PropertyChangeEvent evt) {
509  String eventType = evt.getPropertyName();
510  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
517  try {
525  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
526  if (null != eventData
527  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
528  reviewStatusBus.post(eventData);
529  }
530  } catch (NoCurrentCaseException notUsed) {
531  // Case is closed, do nothing.
532  }
533  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
534  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
541  try {
543  refresh(true);
544 
545  } catch (NoCurrentCaseException notUsed) {
546  // Case is closed, do nothing.
547  }
548  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
549  // case was closed. Remove listeners so that we don't get called with a stale case handle
550  if (evt.getNewValue() == null) {
551  removeNotify();
552  skCase = null;
553  }
554  }
555  }
556  };
557 
558  @Subscribe
559  @Override
560  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
561  refresh(true);
562  }
563 
564  @Subscribe
565  @Override
566  void handleDataAdded(ModuleDataEvent event) {
567  refresh(true);
568  }
569 
570  @Override
571  protected void addNotify() {
575  super.addNotify();
576  }
577 
578  @Override
579  protected void removeNotify() {
583  super.removeNotify();
584  }
585 
589  @Override
590  protected boolean createKeys(List<CreditCardViewMode> list) {
591  list.addAll(Arrays.asList(CreditCardViewMode.values()));
592 
593  return true;
594  }
595 
596  @Override
597  protected Node[] createNodesForKey(CreditCardViewMode key) {
598  switch (key) {
599  case BY_BIN:
600  return new Node[]{new ByBINNode()};
601  case BY_FILE:
602  return new Node[]{new ByFileNode()};
603  default:
604  return new Node[0];
605  }
606  }
607  }
608 
613 
619  super(Children.create(new ViewModeFactory(), true), Lookups.singleton(Account.Type.CREDIT_CARD.getDisplayName()));
620  setName(Account.Type.CREDIT_CARD.getDisplayName());
621  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS
622  }
623 
624  @Override
625  public boolean isLeafTypeNode() {
626  return false;
627  }
628 
629  @Override
630  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
631  return visitor.visit(this);
632  }
633 
634  @Override
635  public String getItemType() {
636  return getClass().getName();
637  }
638  }
639 
640  final private class FileWithCCNFactory extends ObservingChildren<FileWithCCN> {
641 
642  private final PropertyChangeListener pcl = new PropertyChangeListener() {
643  @Override
644  public void propertyChange(PropertyChangeEvent evt) {
645  String eventType = evt.getPropertyName();
646  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
653  try {
661  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
662  if (null != eventData
663  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
664  reviewStatusBus.post(eventData);
665  }
666  } catch (NoCurrentCaseException notUsed) {
667  // Case is closed, do nothing.
668  }
669  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
670  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
677  try {
679  refresh(true);
680 
681  } catch (NoCurrentCaseException notUsed) {
682  // Case is closed, do nothing.
683  }
684  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
685  // case was closed. Remove listeners so that we don't get called with a stale case handle
686  if (evt.getNewValue() == null) {
687  removeNotify();
688  skCase = null;
689  }
690  }
691  }
692  };
693 
694  @Override
695  protected void addNotify() {
699  super.addNotify();
700  }
701 
702  @Override
703  protected void removeNotify() {
707  super.removeNotify();
708  }
709 
710  @Subscribe
711  @Override
712  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
713  refresh(true);
714  }
715 
716  @Subscribe
717  @Override
718  void handleDataAdded(ModuleDataEvent event) {
719  refresh(true);
720  }
721 
722  @Override
723  protected boolean createKeys(List<FileWithCCN> list) {
724  String query =
725  "SELECT blackboard_artifacts.obj_id," //NON-NLS
726  + " solr_attribute.value_text AS solr_document_id, "; //NON-NLS
727  if (skCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
728  query += " string_agg(blackboard_artifacts.artifact_id::character varying, ',') AS artifact_IDs, " //NON-NLS
729  + " string_agg(blackboard_artifacts.review_status_id::character varying, ',') AS review_status_ids, ";
730  } else {
731  query += " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS
732  + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids, ";
733  }
734  query += " COUNT( blackboard_artifacts.artifact_id) AS hits " //NON-NLS
735  + " FROM blackboard_artifacts " //NON-NLS
736  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
737  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
738  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
739  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
740  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
741  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
743  + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS
744  + " ORDER BY hits DESC "; //NON-NLS
745  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
746  ResultSet resultSet = results.getResultSet();) {
747  while (resultSet.next()) {
748  list.add(new FileWithCCN(
749  resultSet.getLong("obj_id"), //NON-NLS
750  resultSet.getString("solr_document_id"), //NON-NLS
751  unGroupConcat(resultSet.getString("artifact_IDs"), Long::valueOf), //NON-NLS
752  resultSet.getLong("hits"), //NON-NLS
753  new HashSet<>(unGroupConcat(resultSet.getString("review_status_ids"), reviewStatusID -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(reviewStatusID)))))); //NON-NLS
754  }
755  } catch (TskCoreException | SQLException ex) {
756  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
757 
758  }
759  return true;
760  }
761 
762  @Override
763  protected Node[] createNodesForKey(FileWithCCN key) {
764  //add all account artifacts for the file and the file itself to the lookup
765  try {
766  List<Object> lookupContents = new ArrayList<>();
767  for (long artId : key.artifactIDs) {
768  lookupContents.add(skCase.getBlackboardArtifact(artId));
769  }
770  AbstractFile abstractFileById = skCase.getAbstractFileById(key.getObjID());
771  lookupContents.add(abstractFileById);
772  return new Node[]{new FileWithCCNNode(key, abstractFileById, lookupContents.toArray())};
773  } catch (TskCoreException ex) {
774  LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS
775  return new Node[0];
776  }
777  }
778  }
779 
784  final public class ByFileNode extends DisplayableItemNode {
785 
789  private ByFileNode() {
790  super(Children.create(new FileWithCCNFactory(), true), Lookups.singleton("By File"));
791  setName("By File"); //NON-NLS
792  updateDisplayName();
793  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS
794  reviewStatusBus.register(this);
795  }
796 
797  @NbBundle.Messages({
798  "# {0} - number of children",
799  "Accounts.ByFileNode.displayName=By File ({0})"})
800  private void updateDisplayName() {
801  String query =
802  "SELECT count(*) FROM ( SELECT count(*) AS documents "
803  + " FROM blackboard_artifacts " //NON-NLS
804  + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS
805  + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS
806  + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS
807  + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS
808  + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS
809  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
811  + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo";
812  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
813  ResultSet resultSet = results.getResultSet();) {
814  while (resultSet.next()) {
815  if (skCase.getDatabaseType().equals(DbType.POSTGRESQL)) {
816  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count")));
817  } else {
818  setDisplayName(Bundle.Accounts_ByFileNode_displayName(resultSet.getLong("count(*)")));
819  }
820  }
821  } catch (TskCoreException | SQLException ex) {
822  LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS
823 
824  }
825  }
826 
827  @Override
828  public boolean isLeafTypeNode() {
829  return true;
830  }
831 
832  @Override
833  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
834  return visitor.visit(this);
835  }
836 
837  @Override
838  public String getItemType() {
839  return getClass().getName();
840  }
841 
842  @Subscribe
843  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
844  updateDisplayName();
845  }
846 
847  @Subscribe
848  void handleDataAdded(ModuleDataEvent event) {
849  updateDisplayName();
850  }
851  }
852 
853  final private class BINFactory extends ObservingChildren<BinResult> {
854 
855  private final PropertyChangeListener pcl = new PropertyChangeListener() {
856  @Override
857  public void propertyChange(PropertyChangeEvent evt) {
858  String eventType = evt.getPropertyName();
859  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
866  try {
874  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
875  if (null != eventData
876  && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
877  reviewStatusBus.post(eventData);
878  }
879  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
880  // Case is closed, do nothing.
881  }
882  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
883  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
890  try {
892 
893  refresh(true);
894  } catch (NoCurrentCaseException notUsed) { //NOPMD empy catch clause
895  // Case is closed, do nothing.
896  }
897  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())
898  && (evt.getNewValue() == null)) {
899  // case was closed. Remove listeners so that we don't get called with a stale case handle
900  removeNotify();
901  skCase = null;
902  }
903  }
904  };
905 
906  @Override
907  protected void addNotify() {
911  super.addNotify();
912  }
913 
914  @Override
915  protected void removeNotify() {
919  super.removeNotify();
920  }
921 
922  @Subscribe
923  @Override
924  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
925  refresh(true);
926  }
927 
928  @Subscribe
929  @Override
930  void handleDataAdded(ModuleDataEvent event) {
931  refresh(true);
932  }
933 
934  @Override
935  protected boolean createKeys(List<BinResult> list) {
936 
937  RangeMap<Integer, BinResult> binRanges = TreeRangeMap.create();
938 
939  String query =
940  "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS
941  + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS
942  + " FROM blackboard_artifacts " //NON-NLS
943  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
944  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
945  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
947  + " GROUP BY BIN " //NON-NLS
948  + " ORDER BY BIN "; //NON-NLS
949  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
950  ResultSet resultSet = results.getResultSet();) {
951  //sort all te individual bins in to the ranges
952  while (resultSet.next()) {
953  final Integer bin = Integer.valueOf(resultSet.getString("BIN"));
954  long count = resultSet.getLong("count");
955 
956  BINRange binRange = (BINRange) CreditCards.getBINInfo(bin);
957  BinResult previousResult = binRanges.get(bin);
958 
959  if (previousResult != null) {
960  binRanges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd()));
961  count += previousResult.getCount();
962  }
963 
964  if (binRange == null) {
965  binRanges.put(Range.closed(bin, bin), new BinResult(count, bin, bin));
966  } else {
967  binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange));
968  }
969  }
970  binRanges.asMapOfRanges().values().forEach(list::add);
971  } catch (TskCoreException | SQLException ex) {
972  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
973  }
974 
975  return true;
976  }
977 
978  @Override
979  protected Node[] createNodesForKey(BinResult key) {
980  return new Node[]{new BINNode(key)};
981  }
982  }
983 
988  final public class ByBINNode extends DisplayableItemNode {
989 
993  @NbBundle.Messages("Accounts.ByBINNode.name=By BIN")
994  private ByBINNode() {
995  super(Children.create(new BINFactory(), true), Lookups.singleton(Bundle.Accounts_ByBINNode_name()));
996  setName(Bundle.Accounts_ByBINNode_name()); //NON-NLS
997  updateDisplayName();
998  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
999  reviewStatusBus.register(this);
1000  }
1001 
1002  @NbBundle.Messages({
1003  "# {0} - number of children",
1004  "Accounts.ByBINNode.displayName=By BIN ({0})"})
1005  private void updateDisplayName() {
1006  String query =
1007  "SELECT count(distinct SUBSTR(blackboard_attributes.value_text,1,8)) AS BINs " //NON-NLS
1008  + " FROM blackboard_artifacts " //NON-NLS
1009  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS
1010  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
1011  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1012  + getRejectedArtifactFilterClause(); //NON-NLS
1013  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1014  ResultSet resultSet = results.getResultSet();) {
1015  while (resultSet.next()) {
1016  setDisplayName(Bundle.Accounts_ByBINNode_displayName(resultSet.getLong("BINs")));
1017  }
1018  } catch (TskCoreException | SQLException ex) {
1019  LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
1020  }
1021  }
1022 
1023  @Override
1024  public boolean isLeafTypeNode() {
1025  return false;
1026  }
1027 
1028  @Override
1029  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1030  return visitor.visit(this);
1031  }
1032 
1033  @Override
1034  public String getItemType() {
1035  return getClass().getName();
1036  }
1037 
1038  @Subscribe
1039  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1040  updateDisplayName();
1041  }
1042 
1043  @Subscribe
1044  void handleDataAdded(ModuleDataEvent event) {
1045  updateDisplayName();
1046  }
1047  }
1048 
1053  @Immutable
1054  final private static class FileWithCCN {
1055 
1056  @Override
1057  public int hashCode() {
1058  int hash = 5;
1059  hash = 79 * hash + (int) (this.objID ^ (this.objID >>> 32));
1060  hash = 79 * hash + Objects.hashCode(this.keywordSearchDocID);
1061  hash = 79 * hash + Objects.hashCode(this.artifactIDs);
1062  hash = 79 * hash + (int) (this.hits ^ (this.hits >>> 32));
1063  hash = 79 * hash + Objects.hashCode(this.statuses);
1064  return hash;
1065  }
1066 
1067  @Override
1068  public boolean equals(Object obj) {
1069  if (this == obj) {
1070  return true;
1071  }
1072  if (obj == null) {
1073  return false;
1074  }
1075  if (getClass() != obj.getClass()) {
1076  return false;
1077  }
1078  final FileWithCCN other = (FileWithCCN) obj;
1079  if (this.objID != other.objID) {
1080  return false;
1081  }
1082  if (this.hits != other.hits) {
1083  return false;
1084  }
1085  if (!Objects.equals(this.keywordSearchDocID, other.keywordSearchDocID)) {
1086  return false;
1087  }
1088  if (!Objects.equals(this.artifactIDs, other.artifactIDs)) {
1089  return false;
1090  }
1091  if (!Objects.equals(this.statuses, other.statuses)) {
1092  return false;
1093  }
1094  return true;
1095  }
1096 
1097  private final long objID;
1098  private final String keywordSearchDocID;
1099  private final List<Long> artifactIDs;
1100  private final long hits;
1101  private final Set<BlackboardArtifact.ReviewStatus> statuses;
1102 
1103  private FileWithCCN(long objID, String solrDocID, List<Long> artifactIDs, long hits, Set<BlackboardArtifact.ReviewStatus> statuses) {
1104  this.objID = objID;
1105  this.keywordSearchDocID = solrDocID;
1106  this.artifactIDs = artifactIDs;
1107  this.hits = hits;
1108  this.statuses = statuses;
1109  }
1110 
1116  public long getObjID() {
1117  return objID;
1118  }
1119 
1126  public String getkeywordSearchDocID() {
1127  return keywordSearchDocID;
1128  }
1129 
1135  public List<Long> getArtifactIDs() {
1136  return artifactIDs;
1137  }
1138 
1144  public long getHits() {
1145  return hits;
1146  }
1147 
1153  public Set<BlackboardArtifact.ReviewStatus> getStatuses() {
1154  return statuses;
1155  }
1156  }
1157 
1174  static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
1175  return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
1176  : Stream.of(groupConcat.split(",")) //NON-NLS
1177  .map(mapper::apply)
1178  .collect(Collectors.toList());
1179  }
1180 
1184  final public class FileWithCCNNode extends DisplayableItemNode {
1185 
1186  private final FileWithCCN fileKey;
1187  private final String fileName;
1188 
1198  @NbBundle.Messages({
1199  "# {0} - raw file name",
1200  "# {1} - solr chunk id",
1201  "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"})
1202  private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) {
1203  super(Children.LEAF, Lookups.fixed(lookupContents));
1204  this.fileKey = key;
1205  this.fileName = (key.getkeywordSearchDocID() == null)
1206  ? content.getName()
1207  : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getkeywordSearchDocID(), "_")); //NON-NLS
1208  setName(fileName + key.getObjID());
1209  setDisplayName(fileName);
1210  }
1211 
1212  @Override
1213  public boolean isLeafTypeNode() {
1214  return true;
1215  }
1216 
1217  @Override
1218  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1219  return visitor.visit(this);
1220  }
1221 
1222  @Override
1223  public String getItemType() {
1224  return getClass().getName();
1225  }
1226 
1227  @Override
1228  @NbBundle.Messages({
1229  "Accounts.FileWithCCNNode.nameProperty.displayName=File",
1230  "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts",
1231  "Accounts.FileWithCCNNode.statusProperty.displayName=Status",
1232  "Accounts.FileWithCCNNode.noDescription=no description"})
1233  protected Sheet createSheet() {
1234  Sheet sheet = super.createSheet();
1235  Sheet.Set propSet = sheet.get(Sheet.PROPERTIES);
1236  if (propSet == null) {
1237  propSet = Sheet.createPropertiesSet();
1238  sheet.put(propSet);
1239  }
1240 
1241  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1242  Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(),
1243  Bundle.Accounts_FileWithCCNNode_noDescription(),
1244  fileName));
1245  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1246  Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(),
1247  Bundle.Accounts_FileWithCCNNode_noDescription(),
1248  fileKey.getHits()));
1249  propSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1250  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1251  Bundle.Accounts_FileWithCCNNode_noDescription(),
1252  fileKey.getStatuses().stream()
1253  .map(BlackboardArtifact.ReviewStatus::getDisplayName)
1254  .collect(Collectors.joining(", ")))); //NON-NLS
1255 
1256  return sheet;
1257  }
1258 
1259  @Override
1260  public Action[] getActions(boolean context) {
1261  Action[] actions = super.getActions(context);
1262  ArrayList<Action> arrayList = new ArrayList<>();
1263  arrayList.addAll(Arrays.asList(actions));
1264  try {
1265  arrayList.addAll(DataModelActionsFactory.getActions(Accounts.this.skCase.getContentById(fileKey.getObjID()), false));
1266  } catch (TskCoreException ex) {
1267  LOGGER.log(Level.SEVERE, "Error gettung content by id", ex);
1268  }
1269 
1270  arrayList.add(approveActionInstance);
1271  arrayList.add(rejectActionInstance);
1272 
1273  return arrayList.toArray(new Action[arrayList.size()]);
1274  }
1275  }
1276 
1277  final private class CreditCardNumberFactory extends ObservingChildren<Long> {
1278 
1279  private final BinResult bin;
1280 
1282  this.bin = bin;
1283  }
1284 
1285  @Subscribe
1286  @Override
1287  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1288  refresh(true);
1289  }
1290 
1291  @Subscribe
1292  @Override
1293  void handleDataAdded(ModuleDataEvent event) {
1294  refresh(true);
1295  }
1296 
1297  @Override
1298  protected boolean createKeys(List<Long> list) {
1299 
1300  String query =
1301  "SELECT blackboard_artifacts.artifact_id " //NON-NLS
1302  + " FROM blackboard_artifacts " //NON-NLS
1303  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1304  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
1305  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1306  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1308  + " ORDER BY blackboard_attributes.value_text"; //NON-NLS
1309  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1310  ResultSet rs = results.getResultSet();) {
1311  while (rs.next()) {
1312  list.add(rs.getLong("artifact_id")); //NON-NLS
1313  }
1314  } catch (TskCoreException | SQLException ex) {
1315  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1316 
1317  }
1318  return true;
1319  }
1320 
1321  @Override
1322  protected Node[] createNodesForKey(Long artifactID) {
1323  if (skCase == null) {
1324  return new Node[0];
1325  }
1326 
1327  try {
1328  BlackboardArtifact art = skCase.getBlackboardArtifact(artifactID);
1329  return new Node[]{new AccountArtifactNode(art)};
1330  } catch (TskCoreException ex) {
1331  LOGGER.log(Level.SEVERE, "Error creating BlackboardArtifactNode for artifact with ID " + artifactID, ex); //NON-NLS
1332  return new Node[0];
1333  }
1334  }
1335  }
1336 
1337  private String getBinRangeString(BinResult bin) {
1338  if (bin.getBINStart() == bin.getBINEnd()) {
1339  return Integer.toString(bin.getBINStart());
1340  } else {
1341  return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + "");
1342  }
1343  }
1344 
1345  final public class BINNode extends DisplayableItemNode {
1346 
1348  private final BinResult bin;
1349 
1350  private BINNode(BinResult bin) {
1351  super(Children.create(new CreditCardNumberFactory(bin), true), Lookups.singleton(getBinRangeString(bin)));
1352  this.bin = bin;
1353  setName(getBinRangeString(bin));
1354  updateDisplayName();
1355  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS
1356  reviewStatusBus.register(this);
1357  }
1358 
1359  @Subscribe
1360  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1361  updateDisplayName();
1362  updateSheet();
1363  }
1364 
1365  @Subscribe
1366  void handleDataAdded(ModuleDataEvent event) {
1367  updateDisplayName();
1368  }
1369 
1370  private void updateDisplayName() {
1371  String query =
1372  "SELECT count(blackboard_artifacts.artifact_id ) AS count" //NON-NLS
1373  + " FROM blackboard_artifacts " //NON-NLS
1374  + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS
1375  + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS
1376  + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS
1377  + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS
1379  try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
1380  ResultSet resultSet = results.getResultSet();) {
1381  while (resultSet.next()) {
1382  setDisplayName(getBinRangeString(bin) + " (" + resultSet.getLong("count") + ")"); //NON-NLS
1383  }
1384  } catch (TskCoreException | SQLException ex) {
1385  LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
1386 
1387  }
1388 
1389  }
1390 
1391  @Override
1392  public boolean isLeafTypeNode() {
1393  return true;
1394  }
1395 
1396  @Override
1397  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
1398  return visitor.visit(this);
1399  }
1400 
1401  @Override
1402  public String getItemType() {
1403  return getClass().getName();
1404  }
1405 
1406  private Sheet.Set getPropertySet(Sheet sheet) {
1407  Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
1408  if (sheetSet == null) {
1409  sheetSet = Sheet.createPropertiesSet();
1410  sheet.put(sheetSet);
1411  }
1412  return sheetSet;
1413  }
1414 
1415  @Override
1416  @NbBundle.Messages({
1417  "Accounts.BINNode.binProperty.displayName=Bank Identifier Number",
1418  "Accounts.BINNode.accountsProperty.displayName=Accounts",
1419  "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type",
1420  "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme",
1421  "Accounts.BINNode.brandProperty.displayName=Brand",
1422  "Accounts.BINNode.bankProperty.displayName=Bank",
1423  "Accounts.BINNode.bankCityProperty.displayName=Bank City",
1424  "Accounts.BINNode.bankCountryProperty.displayName=Bank Country",
1425  "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #",
1426  "Accounts.BINNode.bankURLProperty.displayName=Bank URL",
1427  "Accounts.BINNode.noDescription=no description"})
1428  protected Sheet createSheet() {
1429  Sheet sheet = super.createSheet();
1430  Sheet.Set properties = getPropertySet(sheet);
1431 
1432  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(),
1433  Bundle.Accounts_BINNode_binProperty_displayName(),
1434  Bundle.Accounts_BINNode_noDescription(),
1435  getBinRangeString(bin)));
1436  properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(),
1437  Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1438  bin.getCount()));
1439 
1440  //add optional properties if they are available
1441  if (bin.hasDetails()) {
1442  bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(),
1443  Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1444  cardType)));
1445  bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(),
1446  Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1447  scheme)));
1448  bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(),
1449  Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1450  brand)));
1451  bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(),
1452  Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1453  bankName)));
1454  bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(),
1455  Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1456  bankCity)));
1457  bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(),
1458  Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1459  country)));
1460  bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(),
1461  Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1462  phoneNumber)));
1463  bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(),
1464  Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
1465  url)));
1466  }
1467  return sheet;
1468  }
1469 
1470  private void updateSheet() {
1471  this.setSheet(createSheet());
1472  }
1473 
1474  }
1475 
1480  @Immutable
1481  final static private class BinResult implements CreditCards.BankIdentificationNumber {
1482 
1483  @Override
1484  public int hashCode() {
1485  int hash = 3;
1486  hash = 97 * hash + this.binEnd;
1487  hash = 97 * hash + this.binStart;
1488  return hash;
1489  }
1490 
1491  @Override
1492  public boolean equals(Object obj) {
1493  if (this == obj) {
1494  return true;
1495  }
1496  if (obj == null) {
1497  return false;
1498  }
1499  if (getClass() != obj.getClass()) {
1500  return false;
1501  }
1502  final BinResult other = (BinResult) obj;
1503  if (this.binEnd != other.binEnd) {
1504  return false;
1505  }
1506  if (this.binStart != other.binStart) {
1507  return false;
1508  }
1509  return true;
1510  }
1511 
1513  private final long count;
1514 
1515  private final BINRange binRange;
1516  private final int binEnd;
1517  private final int binStart;
1518 
1519  private BinResult(long count, @Nonnull BINRange binRange) {
1520  this.count = count;
1521  this.binRange = binRange;
1522  binStart = binRange.getBINstart();
1523  binEnd = binRange.getBINend();
1524  }
1525 
1526  private BinResult(long count, int start, int end) {
1527  this.count = count;
1528  this.binRange = null;
1529  binStart = start;
1530  binEnd = end;
1531  }
1532 
1533  int getBINStart() {
1534  return binStart;
1535  }
1536 
1537  int getBINEnd() {
1538  return binEnd;
1539  }
1540 
1541  long getCount() {
1542  return count;
1543  }
1544 
1545  boolean hasDetails() {
1546  return binRange != null;
1547  }
1548 
1549  @Override
1550  public Optional<Integer> getNumberLength() {
1551  return binRange.getNumberLength();
1552  }
1553 
1554  @Override
1555  public Optional<String> getBankCity() {
1556  return binRange.getBankCity();
1557  }
1558 
1559  @Override
1560  public Optional<String> getBankName() {
1561  return binRange.getBankName();
1562  }
1563 
1564  @Override
1565  public Optional<String> getBankPhoneNumber() {
1566  return binRange.getBankPhoneNumber();
1567  }
1568 
1569  @Override
1570  public Optional<String> getBankURL() {
1571  return binRange.getBankURL();
1572  }
1573 
1574  @Override
1575  public Optional<String> getBrand() {
1576  return binRange.getBrand();
1577  }
1578 
1579  @Override
1580  public Optional<String> getCardType() {
1581  return binRange.getCardType();
1582  }
1583 
1584  @Override
1585  public Optional<String> getCountry() {
1586  return binRange.getCountry();
1587  }
1588 
1589  @Override
1590  public Optional<String> getScheme() {
1591  return binRange.getScheme();
1592  }
1593  }
1594 
1595  final private class AccountArtifactNode extends BlackboardArtifactNode {
1596 
1597  private final BlackboardArtifact artifact;
1598 
1599  private AccountArtifactNode(BlackboardArtifact artifact) {
1600  super(artifact, "org/sleuthkit/autopsy/images/credit-card.png"); //NON-NLS
1601  this.artifact = artifact;
1602  setName(Long.toString(this.artifact.getArtifactID()));
1603 
1604  reviewStatusBus.register(this);
1605  }
1606 
1607  @Override
1608  public Action[] getActions(boolean context) {
1609  List<Action> actionsList = new ArrayList<>();
1610  actionsList.addAll(Arrays.asList(super.getActions(context)));
1611 
1612  actionsList.add(approveActionInstance);
1613  actionsList.add(rejectActionInstance);
1614 
1615  return actionsList.toArray(new Action[actionsList.size()]);
1616  }
1617 
1618  @Override
1619  protected Sheet createSheet() {
1620  Sheet sheet = super.createSheet();
1621  Sheet.Set properties = sheet.get(Sheet.PROPERTIES);
1622  if (properties == null) {
1623  properties = Sheet.createPropertiesSet();
1624  sheet.put(properties);
1625  }
1626  properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1627  Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(),
1628  Bundle.Accounts_FileWithCCNNode_noDescription(),
1629  artifact.getReviewStatus().getDisplayName()));
1630 
1631  return sheet;
1632  }
1633 
1634  @Subscribe
1635  void handleReviewStatusChange(ReviewStatusChangeEvent event) {
1636 
1637  // Update the node if event includes this artifact
1638  event.artifacts.stream().filter((art) -> (art.getArtifactID() == this.artifact.getArtifactID())).map((_item) -> {
1639  return _item;
1640  }).forEachOrdered((_item) -> {
1641  updateSheet();
1642  });
1643  }
1644 
1645  private void updateSheet() {
1646  this.setSheet(createSheet());
1647  }
1648 
1649  }
1650 
1651  private final class ToggleShowRejected extends AbstractAction {
1652 
1653  @NbBundle.Messages("ToggleShowRejected.name=Show Rejected Results")
1654  ToggleShowRejected() {
1655  super(Bundle.ToggleShowRejected_name());
1656  }
1657 
1658  @Override
1659  public void actionPerformed(ActionEvent e) {
1660  showRejected = !showRejected;
1661  reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null));
1662  }
1663  }
1664 
1665  private abstract class ReviewStatusAction extends AbstractAction {
1666 
1667  private final BlackboardArtifact.ReviewStatus newStatus;
1668 
1669  private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) {
1670  super(displayName);
1671  this.newStatus = newStatus;
1672 
1673  }
1674 
1675  @Override
1676  public void actionPerformed(ActionEvent e) {
1677 
1678  /* get paths for selected nodes to reselect after applying review
1679  * status change */
1680  List<String[]> selectedPaths = Utilities.actionsGlobalContext().lookupAll(Node.class).stream()
1681  .map(node -> {
1682  String[] createPath;
1683  /*
1684  * If the we are rejecting and not showing rejected
1685  * results, then the selected node, won't exist any
1686  * more, so we select the previous one in stead.
1687  */
1688  if (newStatus == BlackboardArtifact.ReviewStatus.REJECTED && showRejected == false) {
1689  List<Node> siblings = Arrays.asList(node.getParentNode().getChildren().getNodes());
1690  if (siblings.size() > 1) {
1691  int indexOf = siblings.indexOf(node);
1692  //there is no previous for the first node, so instead we select the next one
1693  Node sibling = indexOf > 0
1694  ? siblings.get(indexOf - 1)
1695  : siblings.get(Integer.max(indexOf + 1, siblings.size() - 1));
1696  createPath = NodeOp.createPath(sibling, null);
1697  } else {
1698  /* if there are no other siblings to select,
1699  * just return null, but note we need to filter
1700  * this out of stream below */
1701  return null;
1702  }
1703  } else {
1704  createPath = NodeOp.createPath(node, null);
1705  }
1706  //for the reselect to work we need to strip off the first part of the path.
1707  return Arrays.copyOfRange(createPath, 1, createPath.length);
1708  })
1709  .filter(Objects::nonNull)
1710  .collect(Collectors.toList());
1711 
1712  //change status of selected artifacts
1713  final Collection<? extends BlackboardArtifact> artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
1714  artifacts.forEach(artifact -> {
1715  try {
1716  artifact.setReviewStatus(newStatus);
1717  } catch (TskCoreException ex) {
1718  LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS
1719  }
1720  });
1721  //post event
1722  reviewStatusBus.post(new ReviewStatusChangeEvent(artifacts, newStatus));
1723 
1724  final DataResultTopComponent directoryListing = DirectoryTreeTopComponent.findInstance().getDirectoryListing();
1725  final Node rootNode = directoryListing.getRootNode();
1726 
1727  //convert paths back to nodes
1728  List<Node> toArray = new ArrayList<>();
1729  selectedPaths.forEach(path -> {
1730  try {
1731  toArray.add(NodeOp.findPath(rootNode, path));
1732  } catch (NodeNotFoundException ex) { //NOPMD empty catch clause
1733  //just ingnore paths taht don't exist. this is expected since we are rejecting
1734  }
1735  });
1736  //select nodes
1737  directoryListing.setSelectedNodes(toArray.toArray(new Node[toArray.size()]));
1738  }
1739  }
1740 
1741  final private class ApproveAccounts extends ReviewStatusAction {
1742 
1743  @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"})
1744  private ApproveAccounts() {
1745  super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED);
1746  }
1747  }
1748 
1749  final private class RejectAccounts extends ReviewStatusAction {
1750 
1751  @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"})
1752  private RejectAccounts() {
1753  super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED);
1754  }
1755  }
1756 
1757  static private class ReviewStatusChangeEvent {
1758 
1759  Collection<? extends BlackboardArtifact> artifacts;
1760  BlackboardArtifact.ReviewStatus newReviewStatus;
1761 
1762  ReviewStatusChangeEvent(Collection<? extends BlackboardArtifact> artifacts, BlackboardArtifact.ReviewStatus newReviewStatus) {
1763  this.artifacts = artifacts;
1764  this.newReviewStatus = newReviewStatus;
1765  }
1766  }
1767 
1773  public static String getIconFilePath(Account.Type type) {
1774 
1775  if (type.equals(Account.Type.CREDIT_CARD)) {
1776  return ICON_BASE_PATH + "credit-card.png";
1777  } else if (type.equals(Account.Type.DEVICE)) {
1778  return ICON_BASE_PATH + "image.png";
1779  } else if (type.equals(Account.Type.EMAIL)) {
1780  return ICON_BASE_PATH + "email.png";
1781  } else if (type.equals(Account.Type.FACEBOOK)) {
1782  return ICON_BASE_PATH + "facebook.png";
1783  } else if (type.equals(Account.Type.INSTAGRAM)) {
1784  return ICON_BASE_PATH + "instagram.png";
1785  } else if (type.equals(Account.Type.MESSAGING_APP)) {
1786  return ICON_BASE_PATH + "messaging.png";
1787  } else if (type.equals(Account.Type.PHONE)) {
1788  return ICON_BASE_PATH + "phone.png";
1789  } else if (type.equals(Account.Type.TWITTER)) {
1790  return ICON_BASE_PATH + "twitter.png";
1791  } else if (type.equals(Account.Type.WEBSITE)) {
1792  return ICON_BASE_PATH + "web-file.png";
1793  } else if (type.equals(Account.Type.WHATSAPP)) {
1794  return ICON_BASE_PATH + "WhatsApp.png";
1795  } else {
1796  //there could be a default icon instead...
1797  throw new IllegalArgumentException("Unknown Account.Type: " + type.getTypeName());
1798  }
1799  }
1800 }
boolean createKeys(List< CreditCardViewMode > list)
Definition: Accounts.java:590
BlackboardArtifact.Type getBlackboardArtifactType()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static synchronized IngestManager getInstance()
Set< BlackboardArtifact.ReviewStatus > getStatuses()
Definition: Accounts.java:1153
static List< Action > getActions(File file, boolean isArtifactSource)
static synchronized BankIdentificationNumber getBINInfo(int bin)
static String getIconFilePath(Account.Type type)
Definition: Accounts.java:1773
void removeIngestJobEventListener(final PropertyChangeListener listener)
final Set< BlackboardArtifact.ReviewStatus > statuses
Definition: Accounts.java:1101
void addIngestJobEventListener(final PropertyChangeListener listener)
BinResult(long count,@Nonnull BINRange binRange)
Definition: Accounts.java:1519
FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents)
Definition: Accounts.java:1202
FileWithCCN(long objID, String solrDocID, List< Long > artifactIDs, long hits, Set< BlackboardArtifact.ReviewStatus > statuses)
Definition: Accounts.java:1103
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:420
ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus)
Definition: Accounts.java:1669
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:465

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