diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 216a8e0f82..62fef4adaf 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -222,6 +222,10 @@ + net.sf.sevenzipjbinding + net.sf.sevenzipjbinding.impl + net.sf.sevenzipjbinding.simple + net.sf.sevenzipjbinding.simple.impl org.sleuthkit.autopsy.actions org.sleuthkit.autopsy.casemodule org.sleuthkit.autopsy.casemodule.events diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 990d83eee5..08a1f26b14 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.TskCoreException; //@ServiceProvider(service = DataResultViewer.class) final class DataResultViewerThumbnail extends AbstractDataResultViewer { + private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); //flag to keep track if images are being loaded private int curPage; @@ -95,7 +96,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); - thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel( + thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( new String[] { Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large() })); @@ -395,11 +396,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { private void switchPage() { - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - } + EventQueue.invokeLater(() -> { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); }); //Note the nodes factories are likely creating nodes in EDT anyway, but worker still helps @@ -437,7 +435,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { ex.getMessage()), NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(d); - logger.log(Level.SEVERE, "Error making thumbnails: " + ex.getMessage()); //NON-NLS + logger.log(Level.SEVERE, "Error making thumbnails: {0}", ex.getMessage()); //NON-NLS } // catch and ignore if we were cancelled catch (java.util.concurrent.CancellationException ex) { } @@ -453,6 +451,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { goToPageField.setEnabled(false); pageNumLabel.setText(""); imagesRangeLabel.setText(""); + thumbnailSizeComboBox.setEnabled(false); } else { pageNumLabel.setText( NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", @@ -464,7 +463,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { pageNextButton.setEnabled(!(curPage == totalPages)); pagePrevButton.setEnabled(!(curPage == 1)); goToPageField.setEnabled(totalPages > 1); - + thumbnailSizeComboBox.setEnabled(true); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 701367e9ba..86f247395e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -18,6 +18,11 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.Accounts; +import org.sleuthkit.autopsy.datamodel._private.FileTypeExtensionFilters; +import org.sleuthkit.autopsy.datamodel._private.RecentFiles; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children.Keys; import org.openide.nodes.Node; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index fbc17420b2..788d6969ec 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -185,13 +185,13 @@ public class BlackboardArtifactNode extends DisplayableItemNode { ss = Sheet.createPropertiesSet(); s.put(ss); } - final String NO_DESCR = NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.noDesc.text"); + final String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text"); Map map = new LinkedHashMap<>(); fillPropertyMap(map, artifact); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.srcFile.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.srcFile.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"), NO_DESCR, this.getDisplayName())); @@ -222,13 +222,13 @@ public class BlackboardArtifactNode extends DisplayableItemNode { actualMimeType = ""; //NON-NLS } } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.ext.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.ext.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.displayName"), NO_DESCR, ext)); ss.put(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.mimeType.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.mimeType.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.displayName"), NO_DESCR, actualMimeType)); } @@ -243,32 +243,32 @@ public class BlackboardArtifactNode extends DisplayableItemNode { if (sourcePath.isEmpty() == false) { ss.put(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.filePath.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.filePath.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.displayName"), NO_DESCR, sourcePath)); } if (Arrays.asList(SHOW_FILE_METADATA).contains(artifactTypeId)) { AbstractFile file = associated instanceof AbstractFile ? (AbstractFile) associated : null; - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getMtime(), file) : "")); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCtime(), file) : "")); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getAtime(), file) : "")); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : "")); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"), "", associated.getSize())); } @@ -287,8 +287,8 @@ public class BlackboardArtifactNode extends DisplayableItemNode { if (dataSourceStr.isEmpty() == false) { ss.put(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.dataSrc.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.dataSrc.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.displayName"), NO_DESCR, dataSourceStr)); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 7eb9bc14f4..3c24e02283 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -118,17 +118,6 @@ FileSize.createSheet.filterType.displayName=Filter Type FileSize.createSheet.filterType.desc=no description FileSize.exception.notSupported.msg=Not supported for this type of Displayable Item\: {0} FileTypeChildren.exception.notSupported.msg=Not supported for this type of Displayable Item\: {0} -FileTypeExtensionFilters.tskImgFilter.text=Images -FileTypeExtensionFilters.tskVideoFilter.text=Videos -FileTypeExtensionFilters.tskAudioFilter.text=Audio -FileTypeExtensionFilters.tskArchiveFilter.text=Archives -FileTypeExtensionFilters.tskDocumentFilter.text=Documents -FileTypeExtensionFilters.tskExecFilter.text=Executable -FileTypeExtensionFilters.autDocHtmlFilter.text=HTML -FileTypeExtensionFilters.autDocOfficeFilter.text=Office -FileTypeExtensionFilters.autoDocPdfFilter.text=PDF -FileTypeExtensionFilters.autDocTxtFilter.text=Plain Text -FileTypeExtensionFilters.autDocRtfFilter.text=Rich Text FileTypeNode.createSheet.filterType.name=Filter Type FileTypeNode.createSheet.filterType.displayName=Filter Type FileTypeNode.createSheet.filterType.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java b/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java new file mode 100644 index 0000000000..aa7b13e66b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java @@ -0,0 +1,171 @@ +package org.sleuthkit.autopsy.datamodel; + +import com.google.common.collect.Range; +import com.google.common.collect.RangeMap; +import com.google.common.collect.TreeRangeMap; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.concurrent.GuardedBy; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel._private.BINRange; + +public class CreditCards { + + //Interface for objects that provide details about one or more BINs. + static public interface BankIdentificationNumber { + + /** + * Get the city of the issuer. + * + * @return the city of the issuer. + */ + Optional getBankCity(); + + /** + * Get the name of the issuer. + * + * @return the name of the issuer. + */ + Optional getBankName(); + + /** + * Get the phone number of the issuer. + * + * @return the phone number of the issuer. + */ + Optional getBankPhoneNumber(); + + /** + * Get the URL of the issuer. + * + * @return the URL of the issuer. + */ + Optional getBankURL(); + + /** + * Get the brand of this BIN range. + * + * @return the brand of this BIN range. + */ + Optional getBrand(); + + /** + * Get the type of card (credit vs debit) for this BIN range. + * + * @return the type of cards in this BIN range. + */ + Optional getCardType(); + + /** + * Get the country of the issuer. + * + * @return the country of the issuer. + */ + Optional getCountry(); + + /** + * Get the length of account numbers in this BIN range. + * + * NOTE: the length is currently unused, and not in the data file for + * any ranges. It could be quite helpfull for validation... + * + * @return the length of account numbers in this BIN range. Or an empty + * Optional if the length is unknown. + * + */ + Optional getNumberLength(); + + /** + * Get the scheme this BIN range uses to amex,visa,mastercard, etc + * + * @return the scheme this BIN range uses. + */ + Optional getScheme(); + } + + private static final Logger LOGGER = Logger.getLogger(CreditCards.class.getName()); + /** + * Range Map from a (ranges of) BINs to data model object with details of + * the BIN, ie, bank name, phone, url, visa/amex/mastercard/..., + */ + @GuardedBy("CreditCards.class") + private final static RangeMap binRanges = TreeRangeMap.create(); + + /** + * Flag for if we have loaded the BINs from the file already. + */ + @GuardedBy("CreditCards.class") + private static boolean binsLoaded = false; + + /** + * Load the BIN range information from disk. If the map has already been + * initialized, don't load again. + */ + synchronized private static void loadBINRanges() { + if (binsLoaded == false) { + try { + InputStreamReader in = new InputStreamReader(CreditCards.class.getResourceAsStream("ranges.csv")); //NON-NLS + CSVParser rangesParser = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in); + + //parse each row and add to range map + for (CSVRecord record : rangesParser) { + + /** + * Because ranges.csv allows both 6 and (the newer) 8 digit + * BINs, but we need a consistent length for the range map, + * we pad all the numbers out to 8 digits + */ + String start = StringUtils.rightPad(record.get("iin_start"), 8, "0"); //pad start with 0's //NON-NLS + + //if there is no end listed, use start, since ranges will be closed. + String end = StringUtils.defaultIfBlank(record.get("iin_end"), start); //NON-NLS + end = StringUtils.rightPad(end, 8, "99"); //pad end with 9's //NON-NLS + + final String numberLength = record.get("number_length"); //NON-NLS + + try { + BINRange binRange = new BINRange(Integer.parseInt(start), + Integer.parseInt(end), + StringUtils.isBlank(numberLength) ? null : Integer.valueOf(numberLength), + record.get("scheme"), //NON-NLS + record.get("brand"), //NON-NLS + record.get("type"), //NON-NLS + record.get("country"), //NON-NLS + record.get("bank_name"), //NON-NLS + record.get("bank_url"), //NON-NLS + record.get("bank_phone"), //NON-NLS + record.get("bank_city")); //NON-NLS + + binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), binRange); + + } catch (NumberFormatException numberFormatException) { + LOGGER.log(Level.WARNING, "Failed to parse BIN range: " + record.toString(), numberFormatException); //NON-NLS + } + binsLoaded = true; + } + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Failed to load BIN ranges form ranges.csv", ex); //NON-NLS + MessageNotifyUtil.Notify.warn("Credit Card Number Discovery", "There was an error loading Bank Identification Number information. Accounts will not have their BINs identified."); + } + } + } + + /** + * Get an BINInfo object with details about the given BIN + * + * @param bin the BIN to get details of. + * + * @return + */ + synchronized static public BankIdentificationNumber getBINInfo(int bin) { + loadBINRanges(); + return binRanges.get(bin); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index e7aa3d8fe4..3ddb6a158d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -18,6 +18,9 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; + /** * Root node to store the data sources in a case */ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 3a1e70b164..92b75059b9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index fe3445728b..45a5abc66e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.De import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode; +import org.sleuthkit.autopsy.datamodel._private.Accounts; /** * Visitor pattern that goes over all nodes in the directory tree. This includes @@ -127,7 +128,7 @@ public interface DisplayableItemNodeVisitor { */ T visit(Accounts.AccountsRootNode accountRootNode); - T visit(Accounts.AccountTypeNode accountTypeNode); + T visit(Accounts.CreditCardNumberAccountTypeNode accountTypeNode); T visit(Accounts.ByBINNode byArtifactNode); @@ -137,6 +138,8 @@ public interface DisplayableItemNodeVisitor { T visit(Accounts.BINNode binNode); + T visit(Accounts.DefaultAccountTypeNode node); + /** * Visitor with an implementable default behavior for all types. Override * specific visit types to not use the default behavior. @@ -350,7 +353,7 @@ public interface DisplayableItemNodeVisitor { } @Override - public T visit(Accounts.AccountTypeNode node) { + public T visit(Accounts.CreditCardNumberAccountTypeNode node) { return defaultVisit(node); } @@ -378,5 +381,9 @@ public interface DisplayableItemNodeVisitor { public T visit(Accounts.BINNode node) { return defaultVisit(node); } + @Override + public T visit(Accounts.DefaultAccountTypeNode node) { + return defaultVisit(node); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index 89e8c5b217..b9626d5646 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.ResultSet; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index e638794da1..561e96d510 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -37,7 +39,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT; @@ -201,7 +203,7 @@ public class ExtractedContent implements AutopsyVisitableItem { doNotShow.add(new BlackboardArtifact.Type(TSK_KEYWORD_HIT)); doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_FILE_HIT)); doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_ARTIFACT_HIT)); - doNotShow.add(new BlackboardArtifact.Type(TSK_CREDIT_CARD_ACCOUNT)); + doNotShow.add(new BlackboardArtifact.Type(TSK_ACCOUNT)); } private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index c5fee7d696..88ac89bdc9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java index e1b7553aba..e03b2ac924 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.FileTypeExtensionFilters; import java.util.List; import java.util.Observable; import java.util.Observer; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java index 3bbbf0b63c..86913224e1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.FileTypeExtensionFilters; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index 2264b95ac6..ab2d6c2492 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.ResultSet; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index b07f5568c0..57239561ba 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.ResultSet; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index 319d9ecf9f..8b155ccf32 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.ResultSet; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java index 391a49f757..d7665b2157 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.RecentFiles; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java index c6c9c72471..903028abed 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java @@ -28,7 +28,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.datamodel.RecentFiles.RecentFilesFilter; +import org.sleuthkit.autopsy.datamodel._private.RecentFiles.RecentFilesFilter; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java index e9d8fe6b63..e4ab6811cf 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java @@ -25,7 +25,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.datamodel.RecentFiles.RecentFilesFilter; +import org.sleuthkit.autopsy.datamodel._private.RecentFiles.RecentFilesFilter; import org.sleuthkit.datamodel.SleuthkitCase; /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java index 7a4f581966..5bb16c5602 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.awt.Desktop; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java index 61e9ad626e..60f0247f97 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import org.sleuthkit.datamodel.SleuthkitCase; /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index aa4688dea0..31bd236ade 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.Accounts; import java.util.Arrays; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index ac85cd8787..1ab1e72ad0 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collections; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java index 4a9dee9886..4e5ef0f027 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import org.sleuthkit.datamodel.SleuthkitCase; /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 4919594378..6f7ec0f2c5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.FileTypeExtensionFilters; import java.util.Arrays; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/Accounts.java similarity index 51% rename from Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/Accounts.java index 65a9080c79..070be60da1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/Accounts.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; import com.google.common.collect.Range; import com.google.common.collect.RangeMap; @@ -24,8 +24,6 @@ import com.google.common.collect.TreeRangeMap; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.IOException; -import java.io.InputStreamReader; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -44,28 +42,31 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; -import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.Immutable; import javax.swing.AbstractAction; import javax.swing.Action; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.CreditCards; +import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; @@ -75,28 +76,13 @@ import org.sleuthkit.datamodel.TskCoreException; * AutopsyVisitableItem for the Accounts section of the tree. All nodes, * factories, and data objects related to accounts are inner classes. */ -public class Accounts extends Observable implements AutopsyVisitableItem { +final public class Accounts extends Observable implements AutopsyVisitableItem { private static final Logger LOGGER = Logger.getLogger(Accounts.class.getName()); - private static final BlackboardArtifact.Type CREDIT_CARD_ACCOUNT_TYPE = new BlackboardArtifact.Type(TSK_CREDIT_CARD_ACCOUNT); - private static final BlackboardAttribute.Type ACCOUNT_NUMBER_TYPE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER); @NbBundle.Messages("AccountsRootNode.name=Accounts") final public static String NAME = Bundle.AccountsRootNode_name(); - /** - * Range Map from a (ranges of) B/IINs to data model object with details of - * the B/IIN, ie, bank name, phone, url, visa/amex/mastercard/..., - */ - @GuardedBy("Accounts.class") - private final static RangeMap iinRanges = TreeRangeMap.create(); - - /** - * Flag for if we have loaded the IINs from the file already. - */ - @GuardedBy("Accounts.class") - private static boolean iinsLoaded = false; - private SleuthkitCase skCase; /** @@ -104,59 +90,6 @@ public class Accounts extends Observable implements AutopsyVisitableItem { */ private boolean showRejected = false; - /** - * Load the IIN range information from disk. If the map has already been - * initialized, don't load again. - */ - synchronized private static void loadIINRanges() { - if (iinsLoaded == false) { - try { - InputStreamReader in = new InputStreamReader(Accounts.class.getResourceAsStream("ranges.csv")); //NON-NLS - CSVParser rangesParser = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in); - - //parse each row and add to range map - for (CSVRecord record : rangesParser) { - - /** - * Because ranges.csv allows both 6 and (the newer) 8 digit - * IINs, but we need a consistent length for the range map, - * we pad all the numbers out to 8 digits - */ - String start = StringUtils.rightPad(record.get("iin_start"), 8, "0"); //pad start with 0's //NON-NLS - - //if there is no end listed, use start, since ranges will be closed. - String end = StringUtils.defaultIfBlank(record.get("iin_end"), start); //NON-NLS - end = StringUtils.rightPad(end, 8, "99"); //pad end with 9's //NON-NLS - - final String numberLength = record.get("number_length"); //NON-NLS - - try { - IINRange iinRange = new IINRange(Integer.parseInt(start), - Integer.parseInt(end), - StringUtils.isBlank(numberLength) ? null : Integer.valueOf(numberLength), - record.get("scheme"), //NON-NLS - record.get("brand"), //NON-NLS - record.get("type"), //NON-NLS - record.get("country"), //NON-NLS - record.get("bank_name"), //NON-NLS - record.get("bank_url"), //NON-NLS - record.get("bank_phone"), //NON-NLS - record.get("bank_city")); //NON-NLS - - iinRanges.put(Range.closed(iinRange.getIINstart(), iinRange.getIINend()), iinRange); - - } catch (NumberFormatException numberFormatException) { - LOGGER.log(Level.WARNING, "Failed to parse IIN range: " + record.toString(), numberFormatException); //NON-NLS - } - iinsLoaded = true; - } - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Failed to load IIN ranges form ranges.csv", ex); //NON-NLS - MessageNotifyUtil.Notify.warn("Credit Card Number Discovery", "There was an error loading Bank Identification Number information. Accounts will not have their BINs identified."); - } - } - } - private final RejectAccounts rejectActionInstance; private final ApproveAccounts approveActionInstance; @@ -165,13 +98,18 @@ public class Accounts extends Observable implements AutopsyVisitableItem { * * @param skCase The SleuthkitCase object to use for db queries. */ - Accounts(SleuthkitCase skCase) { + public Accounts(SleuthkitCase skCase) { this.skCase = skCase; this.rejectActionInstance = new RejectAccounts(); this.approveActionInstance = new ApproveAccounts(); } + @Override + public T accept(AutopsyItemVisitor v) { + return v.visit(this); + } + /** * Get the clause that should be used in order to (not) filter out rejected * results from db queries. @@ -192,23 +130,6 @@ public class Accounts extends Observable implements AutopsyVisitableItem { notifyObservers(); } - /** - * Get an IINInfo object with details about the given IIN - * - * @param iin the IIN to get details of. - * - * @return - */ - synchronized static public IINInfo getIINInfo(int iin) { - loadIINRanges(); - return iinRanges.get(iin); - } - - @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); - } - /** * Gets a new Action that when invoked toggles showing rejected artifacts on * or off. @@ -220,201 +141,6 @@ public class Accounts extends Observable implements AutopsyVisitableItem { return new ToggleShowRejected(); } - //Interface for objects that provide details about one or more IINs. - static public interface IINInfo { - - /** - * Get the city of the issuer. - * - * @return the city of the issuer. - */ - Optional getBankCity(); - - /** - * Get the name of the issuer. - * - * @return the name of the issuer. - */ - Optional getBankName(); - - /** - * Get the phone number of the issuer. - * - * @return the phone number of the issuer. - */ - Optional getBankPhoneNumber(); - - /** - * Get the URL of the issuer. - * - * @return the URL of the issuer. - */ - Optional getBankURL(); - - /** - * Get the brand of this IIN range. - * - * @return the brand of this IIN range. - */ - Optional getBrand(); - - /** - * Get the type of card (credit vs debit) for this IIN range. - * - * @return the type of cards in this IIN range. - */ - Optional getCardType(); - - /** - * Get the country of the issuer. - * - * @return the country of the issuer. - */ - Optional getCountry(); - - /** - * Get the length of account numbers in this IIN range. - * - * NOTE: the length is currently unused, and not in the data file for - * any ranges. It could be quite helpfull for validation... - * - * @return the length of account numbers in this IIN range. Or an empty - * Optional if the length is unknown. - * - */ - Optional getNumberLength(); - - /** - * Get the scheme this IIN range uses to, eg amex,visa,mastercard, etc - * - * @return the scheme this IIN range uses. - */ - Optional getScheme(); - } - - /** - * Details of a range of Issuer/Bank Identifiaction Number(s) (IIN/BIN) used - * by a bank. - */ - @Immutable - static private class IINRange implements IINInfo { - - private final int IINStart; //start of IIN range, 8 digits - private final int IINEnd; // end (incluse ) of IIN rnage, 8 digits - - private final Integer numberLength; // the length of accounts numbers with this IIN, currently unused - - /** - * AMEX, VISA, MASTERCARD, DINERS, DISCOVER, UNIONPAY - */ - private final String scheme; - private final String brand; - - /** - * DEBIT, CREDIT - */ - private final String cardType; - private final String country; - private final String bankName; - private final String bankCity; - private final String bankURL; - private final String bankPhoneNumber; - - /** - * Constructor - * - * @param IIN_start the first IIN in the range, must be 8 digits - * @param IIN_end the last(inclusive) IIN in the range, must be 8 - * digits - * @param number_length the length of account numbers in this IIN range - * @param scheme amex/visa/mastercard/etc - * @param brand the brand of this IIN range - * @param type credit vs debit - * @param country the country of the issuer - * @param bank_name the name of the issuer - * @param bank_url the url of the issuer - * @param bank_phone the phone number of the issuer - * @param bank_city the city of the issuer - */ - private IINRange(int IIN_start, int IIN_end, Integer number_length, String scheme, String brand, String type, String country, String bank_name, String bank_url, String bank_phone, String bank_city) { - this.IINStart = IIN_start; - this.IINEnd = IIN_end; - - this.numberLength = number_length; - this.scheme = StringUtils.defaultIfBlank(scheme, null); - this.brand = StringUtils.defaultIfBlank(brand, null); - this.cardType = StringUtils.defaultIfBlank(type, null); - this.country = StringUtils.defaultIfBlank(country, null); - this.bankName = StringUtils.defaultIfBlank(bank_name, null); - this.bankURL = StringUtils.defaultIfBlank(bank_url, null); - this.bankPhoneNumber = StringUtils.defaultIfBlank(bank_phone, null); - this.bankCity = StringUtils.defaultIfBlank(bank_city, null); - } - - /** - * Get the first IIN in this range - * - * @return the first IIN in this range. - */ - int getIINstart() { - return IINStart; - } - - /** - * Get the last (inclusive) IIN in this range. - * - * @return the last (inclusive) IIN in this range. - */ - int getIINend() { - return IINEnd; - } - - @Override - public Optional getNumberLength() { - return Optional.ofNullable(numberLength); - } - - @Override - public Optional getScheme() { - return Optional.ofNullable(scheme); - } - - @Override - public Optional getBrand() { - return Optional.ofNullable(brand); - } - - @Override - public Optional getCardType() { - return Optional.ofNullable(cardType); - } - - @Override - public Optional getCountry() { - return Optional.ofNullable(country); - } - - @Override - public Optional getBankName() { - return Optional.ofNullable(bankName); - } - - @Override - public Optional getBankURL() { - return Optional.ofNullable(bankURL); - } - - @Override - public Optional getBankPhoneNumber() { - return Optional.ofNullable(bankPhoneNumber); - } - - @Override - public Optional getBankCity() { - return Optional.ofNullable(bankCity); - } - } - /** * Base class for factories that are also observers. * @@ -444,13 +170,131 @@ public class Accounts extends Observable implements AutopsyVisitableItem { * Top-level node for the accounts tree */ @NbBundle.Messages({"Accounts.RootNode.displayName=Accounts"}) - public class AccountsRootNode extends DisplayableItemNode { + final public class AccountsRootNode extends DisplayableItemNode { - AccountsRootNode() { - super(Children.create(new AccountTypeFactory(), true), Lookups.singleton(Accounts.this)); - super.setName(Accounts.NAME); - super.setDisplayName(Bundle.Accounts_RootNode_displayName()); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/account_menu.png"); //NON-NLS + /** + * Creates child nodes for each account type (currently hard coded to + * make one for Credit Cards) + */ + final private class AccountTypeFactory extends ObservingChildFactory { + + /* + * The pcl is in this class because it has the easiest mechanisms to + * add and remove itself during its life cycles. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { + /** + * Checking for a current case is a stop gap measure + * until a different way of handling the closing of + * cases is worked out. Currently, remote events may be + * received for a case that is already closed. + */ + try { + Case.getCurrentCase(); + /** + * Even with the check above, it is still possible + * that the case will be closed in a different + * thread before this code executes. If that + * happens, it is possible for the event to have a + * null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData + && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { + Accounts.this.update(); + } + } catch (IllegalStateException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { + /** + * Checking for a current case is a stop gap measure + * until a different way of handling the closing of + * cases is worked out. Currently, remote events may be + * received for a case that is already closed. + */ + try { + Case.getCurrentCase(); + Accounts.this.update(); + } catch (IllegalStateException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + skCase = null; + } + } + } + }; + + @Override + + protected boolean createKeys(List list) { + + try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery( + "SELECT DISTINCT blackboard_attributes.value_text as account_type " + + " FROM blackboard_attributes " + + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()); + ResultSet resultSet = executeQuery.getResultSet()) { + while (resultSet.next()) { + String accountType = resultSet.getString("account_type"); + list.add(accountType); + } + } catch (TskCoreException | SQLException ex) { + Exceptions.printStackTrace(ex); + return false; + } + return true; + } + + @Override + protected Node createNodeForKey(String key) { + try { + Account.Type accountType = Account.Type.valueOf(key); + switch (accountType) { + case CREDIT_CARD: + return new CreditCardNumberAccountTypeNode(); + default: + return new DefaultAccountTypeNode(key); + } + } catch (IllegalArgumentException ex) { + LOGGER.log(Level.WARNING, "Unknown account type: " + key); + //Flesh out what happens with other account types here. + return new DefaultAccountTypeNode(key); + } + } + + @Override + protected void removeNotify() { + IngestManager.getInstance().removeIngestJobEventListener(pcl); + IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removePropertyChangeListener(pcl); + super.removeNotify(); + } + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(pcl); + IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addPropertyChangeListener(pcl); + super.addNotify(); + Accounts.this.update(); + } + } + + public AccountsRootNode() { + super(Children.LEAF, Lookups.singleton(Accounts.this)); + setChildren(Children.create(new AccountTypeFactory(), true)); + setName(Accounts.NAME); + setDisplayName(Bundle.Accounts_RootNode_displayName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png"); //NON-NLS } @Override @@ -465,112 +309,60 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } - /** - * Creates child nodes for each account type (currently hard coded to make - * one for Credit Cards) - */ - private class AccountTypeFactory extends ObservingChildFactory { + final public class DefaultAccountTypeNode extends DisplayableItemNode { + + final private class DefaultAccountFactory extends ChildFactory.Detachable { + + private final String accountTypeName; + + public DefaultAccountFactory(String accountTypeName) { + this.accountTypeName = accountTypeName; + } - /* - * The pcl is in this class because it has the easiest mechanisms to add - * and remove itself during its life cycles. - */ - private final PropertyChangeListener pcl = new PropertyChangeListener() { @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { - /** - * Checking for a current case is a stop gap measure until a - * different way of handling the closing of cases is worked - * out. Currently, remote events may be received for a case - * that is already closed. - */ - try { - Case.getCurrentCase(); - /** - * Even with the check above, it is still possible that - * the case will be closed in a different thread before - * this code executes. If that happens, it is possible - * for the event to have a null oldValue. - */ - ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); - if (null != eventData && CREDIT_CARD_ACCOUNT_TYPE.equals(eventData.getBlackboardArtifactType())) { - Accounts.this.update(); - } - } catch (IllegalStateException notUsed) { - // Case is closed, do nothing. - } - } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) - || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { - /** - * Checking for a current case is a stop gap measure until a - * different way of handling the closing of cases is worked - * out. Currently, remote events may be received for a case - * that is already closed. - */ - try { - Case.getCurrentCase(); - Accounts.this.update(); - } catch (IllegalStateException notUsed) { - // Case is closed, do nothing. - } - } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { - // case was closed. Remove listeners so that we don't get called with a stale case handle - if (evt.getNewValue() == null) { - removeNotify(); - skCase = null; + protected boolean createKeys(List list) { + + String query + = "SELECT blackboard_artifacts.artifact_id " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + " AND blackboard_attributes.value_text = '" + accountTypeName + "'" //NON-NLS + + getRejectedArtifactFilterClause(); //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + list.add(rs.getLong("artifact_id")); //NON-NLS } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS + return false; + } + return true; + } + + @Override + protected Node createNodeForKey(Long t) { + try { + return new BlackboardArtifactNode(skCase.getBlackboardArtifact(t)); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex); + return null; } } - }; - - @Override - @NbBundle.Messages({"Accounts.AccountTypeFactory.accountType.creditCards=Credit Card Numbers"}) - protected boolean createKeys(List list) { - list.add(Bundle.Accounts_AccountTypeFactory_accountType_creditCards()); - return true; } - @Override - protected Node createNodeForKey(String key) { - return new AccountTypeNode(key); - } - - @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); - super.removeNotify(); - } - - @Override - protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(pcl); - IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); - super.addNotify(); - Accounts.this.update(); - } - } - - /** - * Node for an account type. - * - * NOTE: currently hard coded to work for Credit Card only - */ - public class AccountTypeNode extends DisplayableItemNode { - - private AccountTypeNode(String accountTypeName) { - super(Children.create(new ViewModeFactory(), true)); - super.setName(accountTypeName); + private DefaultAccountTypeNode(String accountTypeName) { + super(Children.LEAF); + setChildren(Children.create(new DefaultAccountFactory(accountTypeName), true)); + setName(accountTypeName); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS } @Override public boolean isLeafTypeNode() { - return false; + return true; } @Override @@ -588,27 +380,52 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } /** - * ChildFactory that makes nodes for the different account organizations (by - * file, by BIN) + * Node for an account type. + * + * NOTE: currently hard coded to work for Credit Card only */ - private class ViewModeFactory extends ObservingChildFactory { + final public class CreditCardNumberAccountTypeNode extends DisplayableItemNode { - @Override - protected boolean createKeys(List list) { - list.addAll(Arrays.asList(CreditCardViewMode.values())); - return true; + /** + * ChildFactory that makes nodes for the different account organizations + * (by file, by BIN) + */ + final private class ViewModeFactory extends ObservingChildFactory { + + @Override + protected boolean createKeys(List list) { + list.addAll(Arrays.asList(CreditCardViewMode.values())); + return true; + } + + @Override + protected Node createNodeForKey(CreditCardViewMode key) { + switch (key) { + case BY_BIN: + return new ByBINNode(); + case BY_FILE: + return new ByFileNode(); + default: + return null; + } + } + } + + private CreditCardNumberAccountTypeNode() { + super(Children.LEAF); + setChildren(Children.create(new ViewModeFactory(), true)); + setName(Account.Type.CREDIT_CARD.getDisplayName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS } @Override - protected Node createNodeForKey(CreditCardViewMode key) { - switch (key) { - case BY_BIN: - return new ByBINNode(); - case BY_FILE: - return new ByFileNode(); - default: - return null; - } + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); } } @@ -616,8 +433,70 @@ public class Accounts extends Observable implements AutopsyVisitableItem { * Node that is the root of the "by file" accounts tree. Its children are * FileWithCCNNodes. */ - public class ByFileNode extends DisplayableItemNode implements Observer { + final public class ByFileNode extends DisplayableItemNode implements Observer { + /** + * Factory for the children of the ByFiles Node. + */ + final private class FileWithCCNFactory extends ObservingChildFactory { + + @Override + protected boolean createKeys(List list) { + String query + = "SELECT blackboard_artifacts.obj_id," //NON-NLS + + " solr_attribute.value_text AS solr_document_id, " //NON-NLS + + " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS + + " COUNT( blackboard_artifacts.artifact_id) AS hits, " //NON-NLS + + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids " + + " FROM blackboard_artifacts " //NON-NLS + + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS + + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS + + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS + + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.name() + "'" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getRejectedArtifactFilterClause() + + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS + + " ORDER BY hits DESC "; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + list.add(new FileWithCCN( + rs.getLong("obj_id"), //NON-NLS + rs.getString("solr_document_id"), //NON-NLS + unGroupConcat(rs.getString("artifact_IDs"), Long::valueOf), //NON-NLS + rs.getLong("hits"), //NON-NLS + new HashSet<>(unGroupConcat(rs.getString("review_status_ids"), id -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(id)))))); //NON-NLS + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS + return false; + } + return true; + } + + @Override + protected Node createNodeForKey(FileWithCCN key) { + //add all account artifacts for the file and the file itself to the lookup + try { + List lookupContents = new ArrayList<>(); + for (long artId : key.artifactIDS) { + lookupContents.add(skCase.getBlackboardArtifact(artId)); + } + AbstractFile abstractFileById = skCase.getAbstractFileById(key.getObjID()); + lookupContents.add(abstractFileById); + return new FileWithCCNNode(key, abstractFileById, lookupContents.toArray()); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS + return null; + } + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + } private final FileWithCCNFactory fileFactory; private ByFileNode() { @@ -659,7 +538,60 @@ public class Accounts extends Observable implements AutopsyVisitableItem { * Node that is the root of the "By BIN" accounts tree. Its children are * BINNodes. */ - public class ByBINNode extends DisplayableItemNode implements Observer { + final public class ByBINNode extends DisplayableItemNode implements Observer { + + /** + * Factory that generates the children of the ByBin node. + */ + final private class BINFactory extends ObservingChildFactory { + + @Override + protected boolean createKeys(List list) { + RangeMap ranges = TreeRangeMap.create(); + + String query + = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS + + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getRejectedArtifactFilterClause() + + " GROUP BY BIN " //NON-NLS + + " ORDER BY BIN "; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) { + ResultSet resultSet = results.getResultSet(); + while (resultSet.next()) { + final Integer bin = Integer.valueOf(resultSet.getString("BIN")); + long count = resultSet.getLong("count"); + + BINRange binRange = (BINRange) CreditCards.getBINInfo(bin); + BinResult previousResult = ranges.get(bin); + + if (previousResult != null) { + ranges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd())); + count += previousResult.getCount(); + } + + if (binRange != null) { + ranges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange)); + } else { + ranges.put(Range.closed(bin, bin), new BinResult(count, bin, bin)); + } + } + ranges.asMapOfRanges().values().forEach(list::add); + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS + return false; + } + return true; + } + + @Override + protected Node createNodeForKey(BinResult key) { + return new BINNode(key); + } + } private final BINFactory binFactory; @@ -703,7 +635,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { * associated accounts. */ @Immutable - private static class FileWithCCN { + final private static class FileWithCCN { private final long objID; private final String solrDocumentId; @@ -763,70 +695,10 @@ public class Accounts extends Observable implements AutopsyVisitableItem { .collect(Collectors.toList()); } - /** - * Factory for the children of the ByFiles Node. - */ - private class FileWithCCNFactory extends ObservingChildFactory { - - @Override - protected boolean createKeys(List list) { - String query - = "SELECT blackboard_artifacts.obj_id," //NON-NLS - + " blackboard_attributes.value_text AS solr_document_id, " //NON-NLS - + " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS - + " COUNT( blackboard_artifacts.artifact_id) AS hits, " //NON-NLS - + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids " - + " FROM blackboard_artifacts " //NON-NLS - + " LEFT JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SOLR_DOCUMENT_ID.getTypeID() //NON-NLS - + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID() //NON-NLS - + getRejectedArtifactFilterClause() - + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS - + " ORDER BY hits DESC "; //NON-NLS - try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); - ResultSet rs = results.getResultSet();) { - while (rs.next()) { - list.add(new FileWithCCN( - rs.getLong("obj_id"), //NON-NLS - rs.getString("solr_document_id"), //NON-NLS - unGroupConcat(rs.getString("artifact_IDs"), Long::valueOf), //NON-NLS - rs.getLong("hits"), //NON-NLS - new HashSet<>(unGroupConcat(rs.getString("review_status_ids"), id -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(id)))))); //NON-NLS - } - } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS - return false; - } - return true; - } - - @Override - protected Node createNodeForKey(FileWithCCN key) { - //add all account artifacts for the file and the file itself to the lookup - try { - List lookupContents = new ArrayList<>(); - for (long artId : key.artifactIDS) { - lookupContents.add(skCase.getBlackboardArtifact(artId)); - } - AbstractFile abstractFileById = skCase.getAbstractFileById(key.getObjID()); - lookupContents.add(abstractFileById); - return new FileWithCCNNode(key, abstractFileById, lookupContents.toArray()); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS - return null; - } - } - - @Override - public void update(Observable o, Object arg) { - refresh(true); - } - } - /** * Node that represents a file or chunk of an unallocated space file. */ - public class FileWithCCNNode extends DisplayableItemNode { + final public class FileWithCCNNode extends DisplayableItemNode { private final FileWithCCN fileKey; private final String fileName; @@ -914,13 +786,60 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } } - public class BINNode extends DisplayableItemNode implements Observer { + final public class BINNode extends DisplayableItemNode implements Observer { + /** + * Creates the nodes for the accounts of a given type + */ + final private class CreditCardNumberFactory extends ObservingChildFactory { + + @Override + protected boolean createKeys(List list) { + + String query + = "SELECT blackboard_artifacts.artifact_id " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + " AND blackboard_attributes.value_text >= \"" + bin.getBINStart() + "\" AND blackboard_attributes.value_text < \"" + (bin.getBINEnd() + 1) + "\"" //NON-NLS + + getRejectedArtifactFilterClause() + + " ORDER BY blackboard_attributes.value_text"; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + list.add(rs.getLong("artifact_id")); //NON-NLS + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS + return false; + } + return true; + } + + @Override + protected Node createNodeForKey(Long artifactID) { + if (skCase == null) { + return null; + } + + try { + BlackboardArtifact art = skCase.getBlackboardArtifact(artifactID); + return new AccountArtifactNode(art); + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Error creating BlackboardArtifactNode for artifact with ID " + artifactID, ex); //NON-NLS + return null; + } + } + } private final BinResult bin; + private final CreditCardNumberFactory accountFactory; private BINNode(BinResult bin) { - super(Children.create(new AccountFactory(bin), true)); + super(Children.LEAF); this.bin = bin; + accountFactory = new CreditCardNumberFactory(); + setChildren(Children.create(accountFactory, true)); setName(getBinRangeString()); updateDisplayName(); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS @@ -937,10 +856,10 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } private String getBinRangeString() { - if (bin.getIINStart() == bin.getIINEnd()) { - return Integer.toString(bin.getIINStart()); + if (bin.getBINStart() == bin.getBINEnd()) { + return Integer.toString(bin.getBINStart()); } else { - return bin.getIINStart() + "-" + StringUtils.difference(bin.getIINStart() + "", bin.getIINEnd() + ""); + return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + ""); } } @@ -1019,203 +938,99 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } } - /** - * Factory that generates the children of the ByBin node. - */ - private class BINFactory extends ObservingChildFactory { - - @Override - protected boolean createKeys(List list) { - RangeMap ranges = TreeRangeMap.create(); - - String query - = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS - + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS - + " FROM blackboard_artifacts " //NON-NLS - + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS - + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID() //NON-NLS - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER.getTypeID() //NON-NLS - + getRejectedArtifactFilterClause() - + " GROUP BY BIN " //NON-NLS - + " ORDER BY BIN "; //NON-NLS - try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) { - ResultSet resultSet = results.getResultSet(); - while (resultSet.next()) { - final Integer bin = Integer.valueOf(resultSet.getString("BIN")); - long count = resultSet.getLong("count"); - - IINRange iinRange = (IINRange) getIINInfo(bin); - BinResult previousResult = ranges.get(bin); - - if (previousResult != null) { - ranges.remove(Range.closed(previousResult.getIINStart(), previousResult.getIINEnd())); - count += previousResult.getCount(); - } - - if (iinRange != null) { - ranges.put(Range.closed(iinRange.getIINstart(), iinRange.getIINend()), new BinResult(count, iinRange)); - } else { - ranges.put(Range.closed(bin, bin), new BinResult(count, bin, bin)); - } - } - ranges.asMapOfRanges().values().forEach(list::add); - } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS - return false; - } - return true; - } - - @Override - protected Node createNodeForKey(BinResult key) { - return new BINNode(key); - } - } - /** * Data model item to back the BINNodes in the tree. Has the number of * accounts found with the BIN. */ @Immutable - static private class BinResult implements IINInfo { + final static private class BinResult implements CreditCards.BankIdentificationNumber { /** * The number of accounts with this BIN */ private final long count; - private final IINRange iinRange; - private final int iinEnd; - private final int iinStart; + private final BINRange binRange; + private final int binEnd; + private final int binStart; - private BinResult(long count, @Nonnull IINRange iinRange) { + private BinResult(long count, @Nonnull BINRange binRange) { this.count = count; - this.iinRange = iinRange; - iinStart = iinRange.getIINstart(); - iinEnd = iinRange.getIINend(); + this.binRange = binRange; + binStart = binRange.getBINstart(); + binEnd = binRange.getBINend(); } private BinResult(long count, int start, int end) { this.count = count; - this.iinRange = null; - iinStart = start; - iinEnd = end; + this.binRange = null; + binStart = start; + binEnd = end; } - int getIINStart() { - return iinStart; + int getBINStart() { + return binStart; } - int getIINEnd() { - return iinEnd; + int getBINEnd() { + return binEnd; } - public long getCount() { + long getCount() { return count; } boolean hasDetails() { - return iinRange != null; + return binRange != null; } @Override public Optional getNumberLength() { - return iinRange.getNumberLength(); + return binRange.getNumberLength(); } @Override public Optional getBankCity() { - return iinRange.getBankCity(); + return binRange.getBankCity(); } @Override public Optional getBankName() { - return iinRange.getBankName(); + return binRange.getBankName(); } @Override public Optional getBankPhoneNumber() { - return iinRange.getBankPhoneNumber(); + return binRange.getBankPhoneNumber(); } @Override public Optional getBankURL() { - return iinRange.getBankURL(); + return binRange.getBankURL(); } @Override public Optional getBrand() { - return iinRange.getBrand(); + return binRange.getBrand(); } @Override public Optional getCardType() { - return iinRange.getCardType(); + return binRange.getCardType(); } @Override public Optional getCountry() { - return iinRange.getCountry(); + return binRange.getCountry(); } @Override public Optional getScheme() { - return iinRange.getScheme(); + return binRange.getScheme(); } } - /** - * Creates the nodes for the accounts of a given type - */ - private class AccountFactory extends ObservingChildFactory { - - private final BinResult bin; - - private AccountFactory(BinResult bin) { - this.bin = bin; - } - - @Override - protected boolean createKeys(List list) { - - String query - = "SELECT blackboard_artifacts.artifact_id " //NON-NLS - + " FROM blackboard_artifacts " //NON-NLS - + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS - + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID() //NON-NLS - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER.getTypeID() //NON-NLS - + " AND blackboard_attributes.value_text >= \"" + bin.getIINStart() + "\" AND blackboard_attributes.value_text < \"" + (bin.getIINEnd() + 1) + "\"" //NON-NLS - + getRejectedArtifactFilterClause() - + " ORDER BY blackboard_attributes.value_text"; //NON-NLS - try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); - ResultSet rs = results.getResultSet();) { - while (rs.next()) { - list.add(rs.getLong("artifact_id")); //NON-NLS - } - } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS - return false; - } - return true; - } - - @Override - protected Node createNodeForKey(Long artifactID) { - if (skCase == null) { - return null; - } - - try { - BlackboardArtifact art = skCase.getBlackboardArtifact(artifactID); - return new AccountArtifactNode(art); - } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "Error creating BlackboardArtifactNode for artifact with ID " + artifactID, ex); //NON-NLS - return null; - } - } - } - - private class AccountArtifactNode extends BlackboardArtifactNode { + final private class AccountArtifactNode extends BlackboardArtifactNode { private final BlackboardArtifact artifact; @@ -1253,7 +1068,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } } - final class ToggleShowRejected extends AbstractAction { + private final class ToggleShowRejected extends AbstractAction { @NbBundle.Messages("ToggleShowRejected.name=Show Rejcted Results") ToggleShowRejected() { @@ -1290,7 +1105,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } } - private class ApproveAccounts extends ReviewStatusAction { + final private class ApproveAccounts extends ReviewStatusAction { @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"}) private ApproveAccounts() { @@ -1298,7 +1113,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } } - private class RejectAccounts extends ReviewStatusAction { + final private class RejectAccounts extends ReviewStatusAction { @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"}) private RejectAccounts() { @@ -1308,8 +1123,8 @@ public class Accounts extends Observable implements AutopsyVisitableItem { @Override public void actionPerformed(ActionEvent e) { super.actionPerformed(e); - Collection lookupAll = Utilities.actionsGlobalContext().lookupAll(Node.class); - System.out.println(lookupAll); + Collection targets = Utilities.actionsGlobalContext().lookupAll(Node.class); + System.out.println(targets); // //find the node to select after the target is removed from the UI // List siblings = Arrays.asList(target.getParentNode().getChildren().getNodes()); // if (siblings.size() <= 1) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyItemVisitor.java similarity index 85% rename from Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyItemVisitor.java index ceb390e88e..bf6e2246ea 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyItemVisitor.java @@ -16,14 +16,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; + +import org.sleuthkit.autopsy.datamodel.DataSources; +import org.sleuthkit.autopsy.datamodel.DeletedContent; +import org.sleuthkit.autopsy.datamodel.EmailExtracted; +import org.sleuthkit.autopsy.datamodel.ExtractedContent; +import org.sleuthkit.autopsy.datamodel.FileSize; +import org.sleuthkit.autopsy.datamodel.HashsetHits; +import org.sleuthkit.autopsy.datamodel.InterestingHits; +import org.sleuthkit.autopsy.datamodel.KeywordHits; +import org.sleuthkit.autopsy.datamodel.Reports; +import org.sleuthkit.autopsy.datamodel.Results; +import org.sleuthkit.autopsy.datamodel.Tags; +import org.sleuthkit.autopsy.datamodel.Views; /** * This visitor goes over the AutopsyVisitableItems, which are currently the * nodes in the tree that are structural and not nodes that are from * Sleuthkit-based data model objects. */ -interface AutopsyItemVisitor { +public interface AutopsyItemVisitor { T visit(DataSources i); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyVisitableItem.java similarity index 91% rename from Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyVisitableItem.java index a8efccb494..3eb949ff01 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyVisitableItem.java @@ -16,13 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; +; /** * AutopsyVisitableItems are the nodes in the directory tree that are for * structure only. They are not associated with content objects. */ -interface AutopsyVisitableItem { +public interface AutopsyVisitableItem { /** * visitor pattern support diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/_private/BINRange.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/BINRange.java new file mode 100644 index 0000000000..4c82cdda95 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/BINRange.java @@ -0,0 +1,128 @@ +package org.sleuthkit.autopsy.datamodel._private; + +import java.util.Optional; +import javax.annotation.concurrent.Immutable; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.datamodel.CreditCards; + +/** + * Details of a range of Bank Identification Number(s) (BIN) used by a bank. + */ +@Immutable +public class BINRange implements CreditCards.BankIdentificationNumber { + + private final int BINStart; //start of BIN range, 8 digits + private final int BINEnd; // end (incluse ) of BIN rnage, 8 digits + + private final Integer numberLength; // the length of accounts numbers with this BIN, currently unused + + /** + * AMEX, VISA, MASTERCARD, DINERS, DISCOVER, UNIONPAY + */ + private final String scheme; + private final String brand; + + /** + * DEBIT, CREDIT + */ + private final String cardType; + private final String country; + private final String bankName; + private final String bankCity; + private final String bankURL; + private final String bankPhoneNumber; + + /** + * Constructor + * + * @param BIN_start the first BIN in the range, must be 8 digits + * @param BIN_end the last(inclusive) BIN in the range, must be 8 + * digits + * @param number_length the length of account numbers in this BIN range + * @param scheme amex/visa/mastercard/etc + * @param brand the brand of this BIN range + * @param type credit vs debit + * @param country the country of the issuer + * @param bank_name the name of the issuer + * @param bank_url the url of the issuer + * @param bank_phone the phone number of the issuer + * @param bank_city the city of the issuer + */ + public BINRange(int BIN_start, int BIN_end, Integer number_length, String scheme, String brand, String type, String country, String bank_name, String bank_url, String bank_phone, String bank_city) { + this.BINStart = BIN_start; + this.BINEnd = BIN_end; + + this.numberLength = number_length; + this.scheme = StringUtils.defaultIfBlank(scheme, null); + this.brand = StringUtils.defaultIfBlank(brand, null); + this.cardType = StringUtils.defaultIfBlank(type, null); + this.country = StringUtils.defaultIfBlank(country, null); + this.bankName = StringUtils.defaultIfBlank(bank_name, null); + this.bankURL = StringUtils.defaultIfBlank(bank_url, null); + this.bankPhoneNumber = StringUtils.defaultIfBlank(bank_phone, null); + this.bankCity = StringUtils.defaultIfBlank(bank_city, null); + } + + /** + * Get the first BIN in this range + * + * @return the first BIN in this range. + */ + public int getBINstart() { + return BINStart; + } + + /** + * Get the last (inclusive) BIN in this range. + * + * @return the last (inclusive) BIN in this range. + */ + public int getBINend() { + return BINEnd; + } + + @Override + public Optional getNumberLength() { + return Optional.ofNullable(numberLength); + } + + @Override + public Optional getScheme() { + return Optional.ofNullable(scheme); + } + + @Override + public Optional getBrand() { + return Optional.ofNullable(brand); + } + + @Override + public Optional getCardType() { + return Optional.ofNullable(cardType); + } + + @Override + public Optional getCountry() { + return Optional.ofNullable(country); + } + + @Override + public Optional getBankName() { + return Optional.ofNullable(bankName); + } + + @Override + public Optional getBankURL() { + return Optional.ofNullable(bankURL); + } + + @Override + public Optional getBankPhoneNumber() { + return Optional.ofNullable(bankPhoneNumber); + } + + @Override + public Optional getBankCity() { + return Optional.ofNullable(bankCity); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/_private/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/_private/Bundle.properties new file mode 100644 index 0000000000..dc778857c5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/Bundle.properties @@ -0,0 +1,12 @@ +FileTypeExtensionFilters.tskImgFilter.text=Images +FileTypeExtensionFilters.tskVideoFilter.text=Videos +FileTypeExtensionFilters.tskAudioFilter.text=Audio +FileTypeExtensionFilters.tskArchiveFilter.text=Archives +FileTypeExtensionFilters.tskDocumentFilter.text=Documents +FileTypeExtensionFilters.tskExecFilter.text=Executable +FileTypeExtensionFilters.autDocHtmlFilter.text=HTML +FileTypeExtensionFilters.autDocOfficeFilter.text=Office +FileTypeExtensionFilters.autoDocPdfFilter.text=PDF +FileTypeExtensionFilters.autDocTxtFilter.text=Plain Text +FileTypeExtensionFilters.autDocRtfFilter.text=Rich Text + diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/FileTypeExtensionFilters.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/FileTypeExtensionFilters.java index 1e2dd473dd..09a650161d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/FileTypeExtensionFilters.java @@ -16,20 +16,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; import java.util.Arrays; import java.util.List; - import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; import org.sleuthkit.datamodel.SleuthkitCase; /** * Filters database results by file extension. */ -class FileTypeExtensionFilters implements AutopsyVisitableItem { +public class FileTypeExtensionFilters implements AutopsyVisitableItem { - private SleuthkitCase skCase; + private final SleuthkitCase skCase; // root node filters public enum RootFilter implements AutopsyVisitableItem, SearchFilterInterface { @@ -53,10 +53,10 @@ class FileTypeExtensionFilters implements AutopsyVisitableItem { NbBundle.getMessage(FileTypeExtensionFilters.class, "FileTypeExtensionFilters.tskExecFilter.text"), Arrays.asList(".exe", ".dll", ".bat", ".cmd", ".com")); //NON-NLS - private int id; - private String name; - private String displayName; - private List filter; + private final int id; + private final String name; + private final String displayName; + private final List filter; private RootFilter(int id, String name, String displayName, List filter) { this.id = id; @@ -110,10 +110,10 @@ class FileTypeExtensionFilters implements AutopsyVisitableItem { NbBundle.getMessage(FileTypeExtensionFilters.class, "FileTypeExtensionFilters.autDocRtfFilter.text"), Arrays.asList(".rtf")); //NON-NLS - private int id; - private String name; - private String displayName; - private List filter; + private final int id; + private final String name; + private final String displayName; + private final List filter; private DocumentFilter(int id, String name, String displayName, List filter) { this.id = id; @@ -157,10 +157,10 @@ class FileTypeExtensionFilters implements AutopsyVisitableItem { ExecutableFilter_CMD(3, "ExecutableFilter_CMD", ".cmd", Arrays.asList(".cmd")), //NON-NLS ExecutableFilter_COM(4, "ExecutableFilter_COM", ".com", Arrays.asList(".com")); //NON-NLS - private int id; - private String name; - private String displayName; - private List filter; + private final int id; + private final String name; + private final String displayName; + private final List filter; private ExecutableFilter(int id, String name, String displayName, List filter) { this.id = id; @@ -208,7 +208,7 @@ class FileTypeExtensionFilters implements AutopsyVisitableItem { return this.skCase; } - interface SearchFilterInterface { + public interface SearchFilterInterface { public String getName(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/RecentFiles.java similarity index 96% rename from Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/RecentFiles.java index cc8b8424cb..d3163d579d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/RecentFiles.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; import org.openide.util.NbBundle; import org.sleuthkit.datamodel.SleuthkitCase; @@ -25,7 +25,7 @@ import org.sleuthkit.datamodel.SleuthkitCase; * Recent files node support NOTE: As of june '15 we do not display this in the * tree. It can be added back when we have filtering in the results area. */ -class RecentFiles implements AutopsyVisitableItem { +public class RecentFiles implements AutopsyVisitableItem { SleuthkitCase skCase; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index d8985ac8a7..5d7981b677 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; -import org.sleuthkit.autopsy.datamodel.Accounts; +import org.sleuthkit.autopsy.datamodel._private.Accounts; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; @@ -497,7 +497,7 @@ public class DataResultFilterNode extends FilterNode { } @Override - public AbstractAction visit(Accounts.AccountTypeNode node) { + public AbstractAction visit(Accounts.CreditCardNumberAccountTypeNode node) { return openChild(node); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 987c2bc827..6db8d46a6e 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -57,7 +57,7 @@ import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.Accounts; +import org.sleuthkit.autopsy.datamodel._private.Accounts; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.DataSources; import org.sleuthkit.autopsy.datamodel.DataSourcesNode; diff --git a/Core/src/org/sleuthkit/autopsy/images/account_menu.png b/Core/src/org/sleuthkit/autopsy/images/accounts.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/images/account_menu.png rename to Core/src/org/sleuthkit/autopsy/images/accounts.png diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index ea32f57537..f3057a946d 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -254,8 +254,8 @@ class ReportHTML implements TableReportModule { case TSK_REMOTE_DRIVE: in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS break; - case TSK_CREDIT_CARD_ACCOUNT: - in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/credit-card.png"); //NON-NLS + case TSK_ACCOUNT: + in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS break; default: logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = " + dataType); //NON-NLS @@ -264,7 +264,17 @@ class ReportHTML implements TableReportModule { iconFilePath = path + File.separator + iconFileName; break; } - } else { // no defined artifact found for this dataType + } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) { + /* TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE + * attribute, with a synthetic compound dataType name, so they are + * not caught by the switch statement above. For now we just give + * them all the general account icon, but we could do something else + * in the future. + */ + in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS + iconFileName = "accounts.png"; //NON-NLS + iconFilePath = path + File.separator + iconFileName; + } else { // no defined artifact found for this dataType in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS iconFileName = "star.png"; //NON-NLS iconFilePath = path + File.separator + iconFileName; diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index a7ad4b1415..8ba53127d5 100755 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -18,12 +18,16 @@ */ package org.sleuthkit.autopsy.report; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimaps; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -43,6 +47,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.Type; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; @@ -119,10 +124,10 @@ class TableReportGenerator { */ private void makeBlackboardArtifactTables() { // Make a comment string describing the tag names filter in effect. - StringBuilder comment = new StringBuilder(); + String comment = ""; if (!tagNamesFilter.isEmpty()) { - comment.append(NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text")); - comment.append(makeCommaSeparatedList(tagNamesFilter)); + comment += NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text"); + comment += makeCommaSeparatedList(tagNamesFilter); } // Add a table to the report for every enabled blackboard artifact type. @@ -139,10 +144,10 @@ class TableReportGenerator { // Keyword hits and hashset hit artifacts get special handling. if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - writeKeywordHits(tableReport, comment.toString(), tagNamesFilter); + writeKeywordHits(tableReport, comment, tagNamesFilter); continue; } else if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { - writeHashsetHits(tableReport, comment.toString(), tagNamesFilter); + writeHashsetHits(tableReport, comment, tagNamesFilter); continue; } @@ -152,54 +157,92 @@ class TableReportGenerator { continue; } - /* - Gets all of the attribute types of this artifact type by adding - all of the types to a set - */ - Set attrTypeSet = new TreeSet<>((BlackboardAttribute.Type o1, BlackboardAttribute.Type o2) -> o1.getDisplayName().compareTo(o2.getDisplayName())); - for (ArtifactData data : artifactList) { - List attributes = data.getAttributes(); - for (BlackboardAttribute attribute : attributes) { - attrTypeSet.add(attribute.getAttributeType()); + /* TSK_ACCOUNT artifacts get grouped by their TSK_ACCOUNT_TYPE + * attribute, and then handed off to the standard method for writing + * tables. */ + if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { + //Group account artifacts by their account type + ListMultimap groupedArtifacts = Multimaps.index(artifactList, + artifactData -> { + try { + return artifactData.getArtifact().getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)).getValueString(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Unable to get value of TSK_ACCOUNT_TYPE attribute. Defaulting to \"unknown\"", ex); + return "unknown"; + } + }); + for (String accountType : groupedArtifacts.keySet()) { + /* If the report is a ReportHTML, the data type name + * eventualy makes it to useDataTypeIcon which expects but + * does not require a artifact name, so we make a synthetic + * compund name by appending a ":" and the account type. + */ + final String compundDataTypeName = BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName() + ": " + accountType; + writeTableForDataType(groupedArtifacts.get(accountType), type, compundDataTypeName, comment); } + } else { + //all other artifact types are sent to writeTableForDataType directly + writeTableForDataType(artifactList, type, type.getDisplayName(), comment); } - // Get the columns appropriate for the artifact type. This is - // used to get the data that will be in the cells below based on - // type, and display the column headers. - List columns = getArtifactTableColumns(type.getTypeID(), attrTypeSet); - if (columns.isEmpty()) { - continue; - } - columnHeaderMap.put(type.getTypeID(), columns); - - // The artifact list is sorted now, as getting the row data is - // dependent on having the columns, which is necessary for - // sorting. - Collections.sort(artifactList); - List columnHeaderNames = new ArrayList<>(); - for (Column currColumn : columns) { - columnHeaderNames.add(currColumn.getColumnHeader()); - } - - tableReport.startDataType(type.getDisplayName(), comment.toString()); - tableReport.startTable(columnHeaderNames); - for (ArtifactData artifactData : artifactList) { - // Get the row data for this artifact, and has the - // module add it. - List rowData = artifactData.getRow(); - if (rowData.isEmpty()) { - continue; - } - - tableReport.addRow(rowData); - } - // Finish up this data type - progressPanel.increment(); - tableReport.endTable(); - tableReport.endDataType(); } } + /** + * + * Write the given list of artifacts to the table for the given type. + * + * @param artifactList The List of artifacts to include in the table. + * @param type The Type of artifacts included in the table. All the + * artifacts in artifactList should be of this type. + * @param tableName The name of the table. + * @param comment A comment to put in the header. + */ + private void writeTableForDataType(List artifactList, BlackboardArtifact.Type type, String tableName, String comment) { + /* + * Make a sorted set of all of the attribute types that are on any of + * the given artifacts. + */ + Set attrTypeSet = new TreeSet<>(Comparator.comparing(BlackboardAttribute.Type::getDisplayName)); + for (ArtifactData data : artifactList) { + List attributes = data.getAttributes(); + for (BlackboardAttribute attribute : attributes) { + attrTypeSet.add(attribute.getAttributeType()); + } + } + /* Get the columns appropriate for the artifact type. This is used to + * get the data that will be in the cells below based on type, and + * display the column headers. + */ + List columns = getArtifactTableColumns(type.getTypeID(), attrTypeSet); + if (columns.isEmpty()) { + return; + } + columnHeaderMap.put(type.getTypeID(), columns); + + /* The artifact list is sorted now, as getting the row data is dependent + * on having the columns, which is necessary for sorting. + */ + Collections.sort(artifactList); + + tableReport.startDataType(tableName, comment); + tableReport.startTable(Lists.transform(columns, Column::getColumnHeader)); + + for (ArtifactData artifactData : artifactList) { + // Get the row data for this artifact, and has the + // module add it. + List rowData = artifactData.getRow(); + if (rowData.isEmpty()) { + return; + } + + tableReport.addRow(rowData); + } + // Finish up this data type + progressPanel.increment(); + tableReport.endTable(); + tableReport.endDataType(); + } + /** * Make table for tagged files */ @@ -1449,8 +1492,9 @@ class TableReportGenerator { columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.remotePath"), new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REMOTE_PATH))); - } else if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID()) { + } else if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { columns.add(new StatusColumn()); + attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)); } else { // This is the case that it is a custom type. The reason an else is // necessary is to make sure that the source file column is added @@ -1584,6 +1628,7 @@ class TableReportGenerator { } } + private class AttributeColumn implements Column { private final String columnHeader; @@ -1643,10 +1688,6 @@ class TableReportGenerator { @Override public String getCellData(ArtifactData artData) { return getFileUniquePath(artData.getContent()); - /*else if (this.columnHeader.equals(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))) { - return makeCommaSeparatedList(artData.getTags()); - } - return "";*/ } @Override diff --git a/Core/src/org/sleuthkit/autopsy/report/images/account_menu.png b/Core/src/org/sleuthkit/autopsy/report/images/account_menu.png new file mode 100644 index 0000000000..3a53d894dc Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/report/images/account_menu.png differ diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index 2dfaa0b4ec..f147e16170 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; @@ -127,7 +127,7 @@ public class ExtractedContentViewer implements DataContentViewer { //if the node had artifacts in the lookup use them, other wise look up all credit card artifacts for the content. Collection artifacts = nodeLookup.lookupAll(BlackboardArtifact.class); artifacts = (artifacts == null || artifacts.isEmpty()) - ? content.getArtifacts(TSK_CREDIT_CARD_ACCOUNT) + ? content.getArtifacts(TSK_ACCOUNT) : artifacts; /* @@ -140,7 +140,7 @@ public class ExtractedContentViewer implements DataContentViewer { */ for (BlackboardArtifact artifact : artifacts) { try { - BlackboardAttribute solrIDAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SOLR_DOCUMENT_ID)); + BlackboardAttribute solrIDAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID)); if (solrIDAttr != null) { String valueString = solrIDAttr.getValueString(); if (StringUtils.isNotBlank(valueString)) { @@ -148,7 +148,7 @@ public class ExtractedContentViewer implements DataContentViewer { } } - BlackboardAttribute keyWordAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER)); + BlackboardAttribute keyWordAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); if (keyWordAttr != null) { String valueString = keyWordAttr.getValueString(); if (StringUtils.isNotBlank(valueString)) { @@ -188,7 +188,7 @@ public class ExtractedContentViewer implements DataContentViewer { * For keyword hit artifacts, add the text of the artifact that hit, * not the hit artifact; otherwise add the text for the artifact. */ - if (artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID()) { + if (artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID() || artifact.getArtifactTypeID() == TSK_ACCOUNT.getTypeID()) { try { BlackboardAttribute attribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT_TYPE); if (attribute != null) { @@ -296,7 +296,7 @@ public class ExtractedContentViewer implements DataContentViewer { Collection artifacts = node.getLookup().lookupAll(BlackboardArtifact.class); if (artifacts != null) { for (BlackboardArtifact art : artifacts) { - if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID()) { + if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { return true; } } @@ -321,7 +321,7 @@ public class ExtractedContentViewer implements DataContentViewer { if (art == null) { return 4; } else if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() - || art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID()) { + || art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { return 6; } else { return 4; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java index ab06fc0cbc..028908dc34 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java @@ -122,7 +122,7 @@ abstract class KeywordSearchList { //CCN List ccns = new ArrayList<>(); - ccns.add(new Keyword(CCN_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER)); + ccns.add(new Keyword(CCN_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); lockedLists.add("Credit Card Numbers"); addList("Credit Card Numbers", ccns, true, false, true); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java index eea866a2e3..7dcb605c18 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java @@ -34,8 +34,9 @@ import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.response.TermsResponse.Term; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Version; -import org.sleuthkit.autopsy.datamodel.Accounts; +import org.sleuthkit.autopsy.datamodel.CreditCards; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -52,8 +53,7 @@ final class TermComponentQuery implements KeywordSearchQuery { private static final boolean DEBUG = Version.Type.DEVELOPMENT.equals(Version.getBuildType()); private static final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); - private static final BlackboardAttribute.Type SOLR_DOCUMENT_ID_TYPE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SOLR_DOCUMENT_ID); - private static final BlackboardAttribute.Type ACCOUNT_NUMBER_TYPE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER); + private static final BlackboardAttribute.Type KEYWORD_SEARCH_DOCUMENT_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID); //TODO: move these regex and the luhn check to a new class, something like: CreditCardNumberValidator /* @@ -186,8 +186,13 @@ final class TermComponentQuery implements KeywordSearchQuery { Collection attributes = new ArrayList<>(); try { //if the keyword hit matched the credit card number keyword/regex... - if (keyword.getType() == ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER) { - newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT); + if (keyword.getType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { + newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_ACCOUNT); + final BlackboardAttribute attr = new BlackboardAttribute( + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE), + MODULE_NAME, Account.Type.CREDIT_CARD.name()); + newArtifact.addAttribute(attr); + // make account artifact //try to match it against the track 1 regex Matcher matcher = TRACK1_PATTERN.matcher(hit.getSnippet()); @@ -204,31 +209,31 @@ final class TermComponentQuery implements KeywordSearchQuery { AbstractFile file = (AbstractFile) hit.getContent(); if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { - newArtifact.addAttribute(new BlackboardAttribute(SOLR_DOCUMENT_ID_TYPE, MODULE_NAME, hit.getSolrDocumentId())); + newArtifact.addAttribute(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); } } - String ccn = newArtifact.getAttribute(ACCOUNT_NUMBER_TYPE).getValueString(); - final int iin = Integer.parseInt(ccn.substring(0, 8)); + String ccn = newArtifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)).getValueString(); + final int bin = Integer.parseInt(ccn.substring(0, 8)); - Accounts.IINInfo iinInfo = Accounts.getIINInfo(iin); + CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin); - if (iinInfo != null) { - iinInfo.getScheme().ifPresent(scheme - -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_SCHEME, scheme)); - iinInfo.getCardType().ifPresent(cardType - -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_PAYMENT_CARD_TYPE, cardType)); - iinInfo.getBrand().ifPresent(brand - -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_BRAND, brand)); - iinInfo.getBankName().ifPresent(bankName + if (binInfo != null) { + binInfo.getScheme().ifPresent(scheme + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CARD_SCHEME, scheme)); + binInfo.getCardType().ifPresent(cardType + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CARD_TYPE, cardType)); + binInfo.getBrand().ifPresent(brand + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_BRAND_NAME, brand)); + binInfo.getBankName().ifPresent(bankName -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_BANK_NAME, bankName)); - iinInfo.getBankPhoneNumber().ifPresent(phoneNumber + binInfo.getBankPhoneNumber().ifPresent(phoneNumber -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, phoneNumber)); - iinInfo.getBankURL().ifPresent(url + binInfo.getBankURL().ifPresent(url -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_URL, url)); - iinInfo.getCountry().ifPresent(country + binInfo.getCountry().ifPresent(country -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_COUNTRY, country)); - iinInfo.getBankCity().ifPresent(city + binInfo.getBankCity().ifPresent(city -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CITY, city)); } } else { @@ -323,7 +328,7 @@ final class TermComponentQuery implements KeywordSearchQuery { for (Term term : terms) { final String termStr = KeywordSearchUtil.escapeLuceneQuery(term.getTerm()); - if (keyword.getType() == ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER) { + if (keyword.getType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { //If the keyword is a credit card number, pass it through luhn validator Matcher matcher = CCN_PATTERN.matcher(term.getTerm()); matcher.find(); @@ -383,7 +388,7 @@ final class TermComponentQuery implements KeywordSearchQuery { BlackboardAttribute.Type type = new BlackboardAttribute.Type(attrType); if (artifact.getAttribute(type) == null) { String value = matcher.group(groupName); - if (attrType.equals(ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER)) { + if (attrType.equals(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)) { value = CharMatcher.anyOf(" -").removeFrom(value); } if (StringUtils.isNotBlank(value)) { @@ -404,11 +409,11 @@ final class TermComponentQuery implements KeywordSearchQuery { */ static private void parseTrack2Data(BlackboardArtifact artifact, Matcher matcher) throws IllegalArgumentException, TskCoreException { //try to add all the attrributes common to track 1 and 2 - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER, "accountNumber", matcher); - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_EXPIRATION, "expiration", matcher); - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_SERVICE_CODE, "serviceCode", matcher); - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_DISCRETIONARY, "discretionary", matcher); - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_LRC, "LRC", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_NUMBER, "accountNumber", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_EXPIRATION, "expiration", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_SERVICE_CODE, "serviceCode", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_DISCRETIONARY, "discretionary", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_LRC, "LRC", matcher); }