diff --git a/Core/build.xml b/Core/build.xml index 13952d37ae..79e328cef9 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -32,7 +32,7 @@ ignoreerrors="true" verbose="true"/> - + diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java index 6a6cc516c8..9a8d8b0f8e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java @@ -76,18 +76,29 @@ 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); + @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(); + 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; + /** + * Should rejected accounts be shown in the accounts section of the tree. + */ + private boolean showRejected = false; + /** * Load the IIN range information from disk. If the map has already been * initialized, don't load again. @@ -95,7 +106,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { synchronized private static void loadIINRanges() { if (iinsLoaded == false) { try { - InputStreamReader in = new InputStreamReader(Accounts.class.getResourceAsStream("ranges.csv")); + 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 @@ -106,50 +117,70 @@ public class Accounts extends Observable implements AutopsyVisitableItem { * 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 + 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); - end = StringUtils.rightPad(end, 8, "99"); //pad end with 9's + 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"); + final String numberLength = record.get("number_length"); //NON-NLS try { - IINInfo iinRange = new IINInfo(Integer.parseInt(start), + IINRange iinRange = new IINRange(Integer.parseInt(start), Integer.parseInt(end), StringUtils.isBlank(numberLength) ? null : Integer.valueOf(numberLength), - record.get("scheme"), - record.get("brand"), - record.get("type"), - record.get("country"), - record.get("bank_name"), - record.get("bank_url"), - record.get("bank_phone"), - record.get("bank_city")); + 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); + 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); + 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."); } } } + /** + * Constructor + * + * @param skCase The SleuthkitCase object to use for db queries. + */ + Accounts(SleuthkitCase skCase) { + this.skCase = skCase; + } + + /** + * Get the clause that should be used in order to (not) filter out rejected + * results from db queries. + * + * @return A clause that will or will not filter out rejected artifacts + * based on the state of showRejected. + */ + private String getRejectedArtifactFilterClause() { + return showRejected ? "" : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID(); //NON-NLS + } + + /** + * Notify all observers that something has changed, causing a refresh of the + * accounts section of the tree. + */ private void update() { setChanged(); notifyObservers(); } - Accounts(SleuthkitCase skCase) { - this.skCase = skCase; - } - /** * Are there details available about the given IIN? * @@ -179,6 +210,211 @@ public class Accounts extends Observable implements AutopsyVisitableItem { return v.visit(this); } + /** + * Gets a new Action that when invoked toggles showing rejected artifacts on + * or off. + * + * @return An Action that will toggle whether rejected artifacts are shown + * in the tree rooted by this Accounts instance. + */ + public Action newToggleShowRejectedAction() { + 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. + */ + 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. * @@ -194,13 +430,13 @@ public class Accounts extends Observable implements AutopsyVisitableItem { @Override protected void removeNotify() { super.removeNotify(); - deleteObserver(this); + Accounts.this.deleteObserver(this); } @Override protected void addNotify() { super.addNotify(); - addObserver(this); + Accounts.this.addObserver(this); } } @@ -211,8 +447,8 @@ public class Accounts extends Observable implements AutopsyVisitableItem { public class AccountsRootNode extends DisplayableItemNode { AccountsRootNode() { - super(Children.create(new AccountTypeFactory(), true)); - super.setName("Accounts"); //NON-NLS + 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 } @@ -226,6 +462,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { public T accept(DisplayableItemNodeVisitor v) { return v.visit(this); } + } /** @@ -345,7 +582,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { /** * Enum for the children under the credit card AccountTypeNode. */ - static private enum CreditCardViewMode { + private enum CreditCardViewMode { BY_FILE, BY_BIN; } @@ -440,7 +677,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { "# {0} - number of children", "Accounts.ByBINNode.displayName=By BIN ({0})"}) private void updateDisplayName() { - ArrayList keys = new ArrayList<>(); + ArrayList keys = new ArrayList<>(); binFactory.createKeys(keys); setDisplayName(Bundle.Accounts_ByBINNode_displayName(keys.size())); } @@ -520,7 +757,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { */ static List unGroupConcat(String groupConcat, Function mapper) { return StringUtils.isBlank(groupConcat) ? Collections.emptyList() - : Stream.of(groupConcat.split(",")) + : Stream.of(groupConcat.split(",")) //NON-NLS .map(mapper::apply) .collect(Collectors.toList()); } @@ -542,7 +779,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { + " 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 - + " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() //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); @@ -564,7 +801,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { @Override protected Node createNodeForKey(FileWithCCN key) { - //add all account artifacts for the file and the file itself to th elookup + //add all account artifacts for the file and the file itself to the lookup try { List lookupContents = new ArrayList<>(); for (long artId : key.artifactIDS) { @@ -611,7 +848,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { this.fileKey = key; this.fileName = (key.getSolrDocmentID() == null) ? content.getName() - : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getSolrDocmentID(), "_")); + : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getSolrDocmentID(), "_")); //NON-NLS setName(fileName); setDisplayName(fileName); } @@ -653,7 +890,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { Bundle.Accounts_FileWithCCNNode_noDescription(), fileKey.getStatuses().stream() .map(BlackboardArtifact.ReviewStatus::getDisplayName) - .collect(Collectors.joining(", ")))); + .collect(Collectors.joining(", ")))); //NON-NLS return s; } @@ -671,41 +908,17 @@ public class Accounts extends Observable implements AutopsyVisitableItem { arrayList.add(new RejectAccounts(getLookup().lookupAll(BlackboardArtifact.class))); return arrayList.toArray(new Action[arrayList.size()]); } - } - /** - * Node that represents a BIN (Bank Identification Number) - */ public class BINNode extends DisplayableItemNode implements Observer { - private final BINInfo bin; + private final BinResult bin; private final AccountFactory accountFactory; - private final IINInfo iinRange; - private String brand; - private String city; - private String bankName; - private String phoneNumber; - private String url; - private String country; - private String scheme; - private String cardType; - private BINNode(BINInfo bin) { + private BINNode(BinResult bin) { super(Children.LEAF); this.bin = bin; - iinRange = getIINInfo(bin.getBIN()); - if (iinRange != null) { - cardType = iinRange.getCardType(); - scheme = iinRange.getScheme(); - brand = iinRange.getBrand(); - city = iinRange.getBankCity(); - bankName = iinRange.getBankName(); - phoneNumber = iinRange.getBankPhoneNumber(); - url = iinRange.getBankURL(); - country = iinRange.getCountry(); - } accountFactory = new AccountFactory(bin); setChildren(Children.create(accountFactory, true)); setName(bin.toString()); @@ -722,7 +935,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { private void updateDisplayName() { ArrayList keys = new ArrayList<>(); accountFactory.createKeys(keys); - setDisplayName(bin.getBIN().toString() + " (" + keys.size() + ")"); + setDisplayName(bin.getBIN().toString() + " (" + keys.size() + ")"); //NON-NLS } @Override @@ -735,68 +948,78 @@ public class Accounts extends Observable implements AutopsyVisitableItem { return v.visit(this); } - @Override - @NbBundle.Messages({ - "Accounts.BINNode.binProperty.displayName=Bank Identifier Number", - "Accounts.BINNode.accountsProperty.displayName=Accounts", - "Accounts.BINNode.noDescription=no description"}) - protected Sheet createSheet() { - Sheet s = super.createSheet(); + private Sheet.Set getPropertySet(Sheet s) { Sheet.Set ss = s.get(Sheet.PROPERTIES); if (ss == null) { ss = Sheet.createPropertiesSet(); s.put(ss); } + return ss; + } - ss.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(), + @Override + @NbBundle.Messages({ + "Accounts.BINNode.binProperty.displayName=Bank Identifier Number", + "Accounts.BINNode.accountsProperty.displayName=Accounts", + "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type", + "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme", + "Accounts.BINNode.brandProperty.displayName=Brand", + "Accounts.BINNode.bankProperty.displayName=Bank", + "Accounts.BINNode.bankCityProperty.displayName=Bank City", + "Accounts.BINNode.bankCountryProperty.displayName=Bank Country", + "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #", + "Accounts.BINNode.bankURLProperty.displayName=Bank URL", + "Accounts.BINNode.noDescription=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set properties = getPropertySet(sheet); + + properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(), Bundle.Accounts_BINNode_binProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), bin.getBIN())); - ss.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(), - Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - bin.getCount())); - ss.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(), - Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - bin.getCount())); - ss.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(), + properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), bin.getCount())); - if (StringUtils.isNotBlank(cardType)) { - ss.put(new NodeProperty<>("Payment Card Type", "Payment Card Type", Bundle.Accounts_BINNode_noDescription(), cardType)); + //add optional properties if they are available + if (bin.hasDetails()) { + bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(), + Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + cardType))); + bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(), + Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + scheme))); + bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(), + Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + brand))); + bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(), + Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + bankName))); + bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(), + Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + bankCity))); + bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(), + Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + country))); + bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), + Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + phoneNumber))); + bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(), + Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + url))); } - if (StringUtils.isNotBlank(scheme)) { - ss.put(new NodeProperty<>("Credit Card Scheme", "Credit Card Scheme", Bundle.Accounts_BINNode_noDescription(), scheme)); - } - if (StringUtils.isNotBlank(brand)) { - ss.put(new NodeProperty<>("Brand", "Brand", Bundle.Accounts_BINNode_noDescription(), brand)); - } - if (StringUtils.isNotBlank(bankName)) { - ss.put(new NodeProperty<>("Bank", "Bank", Bundle.Accounts_BINNode_noDescription(), bankName)); - } - if (StringUtils.isNotBlank(city)) { - ss.put(new NodeProperty<>("Bank City", "Bank City", Bundle.Accounts_BINNode_noDescription(), city)); - } - if (StringUtils.isNotBlank(country)) { - ss.put(new NodeProperty<>("Bank Country", "Bank Country", Bundle.Accounts_BINNode_noDescription(), country)); - } - if (StringUtils.isNotBlank(phoneNumber)) { - ss.put(new NodeProperty<>("Bank Phone #", "Bank Phone #", Bundle.Accounts_BINNode_noDescription(), phoneNumber)); - } - if (StringUtils.isNotBlank(url)) { - ss.put(new NodeProperty<>("Bank URL", "Bank URL", Bundle.Accounts_BINNode_noDescription(), url)); - } - return s; + return sheet; } } /** * Factory that generates the children of the ByBin node. */ - private class BINFactory extends ObservingChildFactory { + private class BINFactory extends ObservingChildFactory { @Override - protected boolean createKeys(List list) { + protected boolean createKeys(List list) { String query = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS @@ -804,13 +1027,13 @@ public class Accounts extends Observable implements AutopsyVisitableItem { + " 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_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() //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()) { - list.add(new BINInfo(Integer.valueOf(resultSet.getString("BIN")), //NON-NLS + list.add(new BinResult(Integer.valueOf(resultSet.getString("BIN")), //NON-NLS resultSet.getLong("count"))); //NON-NLS } } catch (TskCoreException | SQLException ex) { @@ -821,26 +1044,28 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } @Override - protected Node createNodeForKey(BINInfo key) { + protected Node createNodeForKey(BinResult key) { return new BINNode(key); } } /** - * Data model item to back the BINNodes in the tree. Has basic info about a - * BIN. + * Data model item to back the BINNodes in the tree. Has the number of + * accounts found with the BIN. */ - private class BINInfo { + private class BinResult implements IINInfo { private final Integer bin; /** * The number of accounts with this BIN */ private final Long count; + private final IINInfo iinInfo; - private BINInfo(Integer bin, Long count) { + private BinResult(Integer bin, Long count) { this.bin = bin; this.count = count; + iinInfo = getIINInfo(bin); } public Integer getBIN() { @@ -850,6 +1075,55 @@ public class Accounts extends Observable implements AutopsyVisitableItem { public Long getCount() { return count; } + + boolean hasDetails() { + return iinInfo != null; + } + + @Override + public Optional getNumberLength() { + return iinInfo.getNumberLength(); + } + + @Override + public Optional getBankCity() { + return iinInfo.getBankCity(); + } + + @Override + public Optional getBankName() { + return iinInfo.getBankName(); + } + + @Override + public Optional getBankPhoneNumber() { + return iinInfo.getBankPhoneNumber(); + } + + @Override + public Optional getBankURL() { + return iinInfo.getBankURL(); + } + + @Override + public Optional getBrand() { + return iinInfo.getBrand(); + } + + @Override + public Optional getCardType() { + return iinInfo.getCardType(); + } + + @Override + public Optional getCountry() { + return iinInfo.getCountry(); + } + + @Override + public Optional getScheme() { + return iinInfo.getScheme(); + } } /** @@ -857,9 +1131,9 @@ public class Accounts extends Observable implements AutopsyVisitableItem { */ private class AccountFactory extends ObservingChildFactory { - private final BINInfo bin; + private final BinResult bin; - private AccountFactory(BINInfo bin) { + private AccountFactory(BinResult bin) { this.bin = bin; } @@ -872,7 +1146,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem { + " 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 LIKE \"" + bin.getBIN() + "%\" " //NON-NLS - + " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() //NON-NLS + + getRejectedArtifactFilterClause() + " ORDER BY blackboard_attributes.value_text"; //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet rs = results.getResultSet();) { @@ -922,13 +1196,13 @@ public class Accounts extends Observable implements AutopsyVisitableItem { @Override protected Sheet createSheet() { - Sheet sheet = super.createSheet(); //To change body of generated methods, choose Tools | Templates. - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); + Sheet sheet = super.createSheet(); + Sheet.Set properties = sheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + sheet.put(properties); } - sheetSet.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), Bundle.Accounts_FileWithCCNNode_noDescription(), artifact.getReviewStatus().getDisplayName())); @@ -937,6 +1211,20 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } } + final class ToggleShowRejected extends AbstractAction { + + @NbBundle.Messages("ToggleShowRejected.name=Show Rejcted Results") + ToggleShowRejected() { + super(Bundle.ToggleShowRejected_name()); + } + + @Override + public void actionPerformed(ActionEvent e) { + showRejected = !showRejected; + update(); + } + } + private class ApproveAccounts extends AbstractAction { private final Collection artifacts; @@ -982,165 +1270,4 @@ public class Accounts extends Observable implements AutopsyVisitableItem { } } } - - /** - * Details of a (range of) Issuer/Bank Identifiaction Number(s) * (IIN/BIN) - * used by a bank. - */ - static public class 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 IINInfo(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 = scheme; - this.brand = brand; - this.cardType = type; - this.country = country; - this.bankName = bank_name; - this.bankURL = bank_url; - this.bankPhoneNumber = bank_phone; - this.bankCity = bank_city; - } - - /** - * 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; - } - - /** - * 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. - * - */ - public Optional getNumberLength() { - return Optional.ofNullable(numberLength); - } - - /** - * Get the scheme this IIN range uses to, eg amex,visa,mastercard, etc - * - * @return the scheme this IIN range uses. - */ - public String getScheme() { - return scheme; - } - - /** - * Get the brand of this IIN range. - * - * @return the brand of this IIN range. - */ - public String getBrand() { - return brand; - } - - /** - * Get the type of card (credit vs debit) for this IIN range. - * - * @return the type of cards in this IIN range. - */ - public String getCardType() { - return cardType; - } - - /** - * Get the country of the issuer. - * - * @return the country of the issuer. - */ - public String getCountry() { - return country; - } - - /** - * Get the name of the issuer. - * - * @return the name of the issuer. - */ - public String getBankName() { - return bankName; - } - - /** - * Get the URL of the issuer. - * - * @return the URL of the issuer. - */ - public String getBankURL() { - return bankURL; - } - - /** - * Get the phone number of the issuer. - * - * @return the phone number of the issuer. - */ - public String getBankPhoneNumber() { - return bankPhoneNumber; - } - - /** - * Get the city of the issuer. - * - * @return the city of the issuer. - */ - public String getBankCity() { - return bankCity; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties index aae7628173..31dbbd1b49 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties @@ -93,3 +93,4 @@ ExtractUnallocAction.done.notifyMsg.completedExtract.msg=Files were extracted to ExtractUnallocAction.done.errMsg.title=Error Extracting ExtractUnallocAction.done.errMsg.msg=Error extracting unallocated space\: {0} ExtractAction.done.notifyMsg.extractErr=Error extracting files\: {0} +DirectoryTreeTopComponent.showRejectedCheckBox.text=Show Rejected Results diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index e4821adcb3..9504548823 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -16,28 +16,29 @@ - - + + + - + + + - - - - + + + + - - - - + + @@ -122,7 +123,12 @@ - + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 9d7f7c5df8..987c2bc827 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,10 +57,10 @@ 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.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.DataSources; import org.sleuthkit.autopsy.datamodel.DataSourcesNode; -import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.ExtractedContent; import org.sleuthkit.autopsy.datamodel.KeywordHits; import org.sleuthkit.autopsy.datamodel.KnownFileFilterNode; @@ -164,65 +164,68 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat jScrollPane1 = new BeanTreeView(); backButton = new javax.swing.JButton(); forwardButton = new javax.swing.JButton(); - jSeparator1 = new javax.swing.JSeparator(); + showRejectedCheckBox = new javax.swing.JCheckBox(); jScrollPane1.setBorder(null); - backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N NON-NLS + backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.backButton.text")); // NOI18N backButton.setBorderPainted(false); backButton.setContentAreaFilled(false); - backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled.png"))); // NOI18N NON-NLS + backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled.png"))); // NOI18N backButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); backButton.setMaximumSize(new java.awt.Dimension(55, 100)); backButton.setMinimumSize(new java.awt.Dimension(5, 5)); backButton.setPreferredSize(new java.awt.Dimension(23, 23)); - backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover.png"))); // NOI18N NON-NLS + backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover.png"))); // NOI18N backButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { backButtonActionPerformed(evt); } }); - forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward.png"))); // NOI18N NON-NLS + forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(forwardButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.forwardButton.text")); // NOI18N forwardButton.setBorderPainted(false); forwardButton.setContentAreaFilled(false); - forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled.png"))); // NOI18N NON-NLS + forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled.png"))); // NOI18N forwardButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); forwardButton.setMaximumSize(new java.awt.Dimension(55, 100)); forwardButton.setMinimumSize(new java.awt.Dimension(5, 5)); forwardButton.setPreferredSize(new java.awt.Dimension(23, 23)); - forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover.png"))); // NOI18N NON-NLS + forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover.png"))); // NOI18N forwardButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { forwardButtonActionPerformed(evt); } }); + org.openide.awt.Mnemonics.setLocalizedText(showRejectedCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.showRejectedCheckBox.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addContainerGap() + .addGap(5, 5, 5) .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(206, Short.MAX_VALUE)) - .addComponent(jSeparator1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 46, Short.MAX_VALUE) + .addComponent(showRejectedCheckBox) + .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(5, 5, 5) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(0, 0, 0) - .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 1, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, 0) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 860, Short.MAX_VALUE) + .addComponent(showRejectedCheckBox)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 838, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); }// //GEN-END:initComponents @@ -275,11 +278,12 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.setCursor(null); }//GEN-LAST:event_forwardButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton backButton; private javax.swing.JButton forwardButton; private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JSeparator jSeparator1; + private javax.swing.JCheckBox showRejectedCheckBox; // End of variables declaration//GEN-END:variables /** @@ -356,6 +360,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat items.add(new Tags()); items.add(new Reports()); contentChildren = new RootContentChildren(items); + Node root = new AbstractNode(contentChildren) { /** * to override the right click action in the white blank @@ -399,6 +404,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat tree.expandNode(resultsChilds.findChild(KeywordHits.NAME)); tree.expandNode(resultsChilds.findChild(ExtractedContent.NAME)); + Accounts accounts = resultsChilds.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); + showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); + showRejectedCheckBox.setSelected(false); + Node views = childNodes.findChild(ViewsNode.NAME); Children viewsChilds = views.getChildren(); for (Node n : viewsChilds.getNodes()) { diff --git a/KeywordSearch/build.xml b/KeywordSearch/build.xml index 22d378939f..c63f0c15b0 100644 --- a/KeywordSearch/build.xml +++ b/KeywordSearch/build.xml @@ -36,6 +36,7 @@ + diff --git a/KeywordSearch/nbproject/project.properties b/KeywordSearch/nbproject/project.properties index 0ae8001684..ac11234f18 100644 --- a/KeywordSearch/nbproject/project.properties +++ b/KeywordSearch/nbproject/project.properties @@ -51,6 +51,7 @@ file.reference.vorbis-java-tika-0.1.jar=release/modules/ext/vorbis-java-tika-0.1 file.reference.wstx-asl-3.2.7.jar=release/modules/ext/wstx-asl-3.2.7.jar file.reference.xmlbeans-2.3.0.jar=release/modules/ext/xmlbeans-2.3.0.jar file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar +file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java index 5c7044e2ab..ab06fc0cbc 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java @@ -43,7 +43,7 @@ abstract class KeywordSearchList { private static final String IP_ADDRESS_REGEX = "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"; //NON-NLS private static final String EMAIL_ADDRESS_REGEX = "(?=.{8})[a-z0-9%+_-]+(?:\\.[a-z0-9%+_-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z]{2,4}(? theLists; //the keyword data diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java index ab5cee98ed..4a4b8cf6d9 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java @@ -19,6 +19,7 @@ // package org.sleuthkit.autopsy.keywordsearch; +import com.google.common.base.CharMatcher; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -65,7 +66,7 @@ final class TermComponentQuery implements KeywordSearchQuery { */ private static final Pattern TRACK2_PATTERN = Pattern.compile( "[:;<=>?]?" //(optional)start sentinel //NON-NLS - + "(?[3456]\\d{11,18})" //12-19 digit ccn, first digit is 3, 4, 5, or 6 //NON-NLS + + "(?[3456]([ -]?\\d){11,18})" //12-19 digits, with possible single spaces or dashes in between. first digit is 3,4,5, or 6 //NON-NLS + "(?:[:;<=>?]" //separator //NON-NLS + "(?:(?\\d{4})" //4 digit expiration date YYMM //NON-NLS + "(?:(?\\d{3})" //3 digit service code //NON-NLS @@ -85,7 +86,7 @@ final class TermComponentQuery implements KeywordSearchQuery { "(?:" //begin nested optinal group //NON-NLS + "%?" //optional start sentinal: % //NON-NLS + "B)?" //format code //NON-NLS - + "(?[3456]\\d{11,18})" //12-19 digit ccn, first digit is 3, 4, 5, or 6 //NON-NLS + + "(?[3456]([ -]?\\d){11,18})" //12-19 digits, with possible single spaces or dashes in between. first digit is 3,4,5, or 6 //NON-NLS + "\\^" //separator //NON-NLS + "(?[^^]{2,26})" //2-26 charachter name, not containing ^ //NON-NLS + "(?:\\^" //separator //NON-NLS @@ -95,7 +96,7 @@ final class TermComponentQuery implements KeywordSearchQuery { + "(?:\\?" // end sentinal: ? //NON-NLS + "(?.)" //longitudinal redundancy check //NON-NLS + "?)?)?)?)?)?");//close nested optional groups //NON-NLS - private static final Pattern CCN_PATTERN = Pattern.compile("(?[3456]\\d{11,18})"); //12-19 digit ccn, first digit is 3, 4, 5, or 6 //NON-NLS + private static final Pattern CCN_PATTERN = Pattern.compile("(?[3456]([ -]?\\d){11,18})"); //12-19 digits, with possible single spaces or dashes in between. first digit is 3,4,5, or 6 //NON-NLS private static final LuhnCheckDigit LUHN_CHECK = new LuhnCheckDigit(); //corresponds to field in Solr schema, analyzed with white-space tokenizer only @@ -114,7 +115,6 @@ final class TermComponentQuery implements KeywordSearchQuery { TermComponentQuery(KeywordList keywordList, Keyword keyword) { this.keyword = keyword; - this.keywordList = keywordList; this.escapedQuery = keyword.getQuery(); } @@ -209,28 +209,26 @@ final class TermComponentQuery implements KeywordSearchQuery { String ccn = newArtifact.getAttribute(ACCOUNT_NUMBER_TYPE).getValueString(); final int iin = Integer.parseInt(ccn.substring(0, 8)); - Accounts.IINInfo iinRange = Accounts.getIINInfo(iin); - newArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CREDIT_CARD_SCHEME, MODULE_NAME, iinRange.getScheme())); - newArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PAYMENT_CARD_TYPE, MODULE_NAME, iinRange.getCardType())); - if (StringUtils.isNotBlank(iinRange.getBrand())) { - newArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND, MODULE_NAME, iinRange.getBrand())); - } - if (StringUtils.isNotBlank(iinRange.getBankName())) { - newArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, iinRange.getBankName())); - } - if (StringUtils.isNotBlank(iinRange.getBankPhoneNumber())) { - newArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, iinRange.getBankPhoneNumber())); - } - if (StringUtils.isNotBlank(iinRange.getBankURL())) { - newArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, iinRange.getBankURL())); - } - if (StringUtils.isNotBlank(iinRange.getCountry())) { - newArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, iinRange.getCountry())); - } - if (StringUtils.isNotBlank(iinRange.getBankCity())) { - newArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, iinRange.getBankCity())); - } + Accounts.IINInfo iinInfo = Accounts.getIINInfo(iin); + 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 + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_BANK_NAME, bankName)); + iinInfo.getBankPhoneNumber().ifPresent(phoneNumber + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, phoneNumber)); + iinInfo.getBankURL().ifPresent(url + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_URL, url)); + iinInfo.getCountry().ifPresent(country + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_COUNTRY, country)); + iinInfo.getBankCity().ifPresent(city + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CITY, city)); + } } else { //make keyword hit artifact newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); @@ -269,6 +267,22 @@ final class TermComponentQuery implements KeywordSearchQuery { } } + /** + * Add an attribute of the given type and value to the given artifact, + * catching and logging any exceptions. + * + * @param newArtifact The artifact to add an attribute to. + * @param AtributeType The type of attribute to add. + * @param attributeValue The value of the attribute to add. + */ + static private void addAttributeSafe(BlackboardArtifact newArtifact, ATTRIBUTE_TYPE AtributeType, String attributeValue) { + try { + newArtifact.addAttribute(new BlackboardAttribute(AtributeType, MODULE_NAME, attributeValue)); + } catch (IllegalArgumentException | TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error adding bb attribute to artifact", ex); //NON-NLS + } + } + @Override public QueryResults performQuery() throws NoOpenCoreException { /* @@ -311,7 +325,7 @@ final class TermComponentQuery implements KeywordSearchQuery { //If the keyword is a credit card number, pass it through luhn validator Matcher matcher = CCN_PATTERN.matcher(term.getTerm()); matcher.find(); - final String ccn = matcher.group("ccn"); + final String ccn = CharMatcher.anyOf(" -").removeFrom(matcher.group("ccn")); if (false == LUHN_CHECK.isValid(ccn)) { continue; //if the hit does not pass the luhn check, skip it. } @@ -371,6 +385,9 @@ 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)) { + value = CharMatcher.anyOf(" -").removeFrom(value); + } if (StringUtils.isNotBlank(value)) { artifact.addAttribute(new BlackboardAttribute(type, MODULE_NAME, value)); }