Merge pull request #2329 from millmanorama/group-bins-by-range

Group bins by range
This commit is contained in:
Richard Cordovano 2016-09-12 10:16:54 -04:00 committed by GitHub
commit b09b9d6d25
2 changed files with 85 additions and 33 deletions

View File

@ -43,7 +43,9 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVFormat;
@ -297,6 +299,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
* Details of a range of Issuer/Bank Identifiaction Number(s) (IIN/BIN) used * Details of a range of Issuer/Bank Identifiaction Number(s) (IIN/BIN) used
* by a bank. * by a bank.
*/ */
@Immutable
static private class IINRange implements IINInfo { static private class IINRange implements IINInfo {
private final int IINStart; //start of IIN range, 8 digits private final int IINStart; //start of IIN range, 8 digits
@ -702,6 +705,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
* DataModel for a child of the ByFileNode. Represents a file(chunk) and its * DataModel for a child of the ByFileNode. Represents a file(chunk) and its
* associated accounts. * associated accounts.
*/ */
@Immutable
private static class FileWithCCN { private static class FileWithCCN {
private final long objID; private final long objID;
@ -933,9 +937,15 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
} }
private void updateDisplayName() { private void updateDisplayName() {
ArrayList<Long> keys = new ArrayList<>(); setDisplayName(getBinRangeString() + " (" + bin.getCount() + ")"); //NON-NLS
accountFactory.createKeys(keys); }
setDisplayName(bin.getBIN().toString() + " (" + keys.size() + ")"); //NON-NLS
private String getBinRangeString() {
if (bin.getIINStart() == bin.getIINEnd()) {
return Integer.toString(bin.getIINStart());
} else {
return bin.getIINStart() + "-" + StringUtils.difference(bin.getIINStart() + "", bin.getIINEnd() + "");
}
} }
@Override @Override
@ -977,7 +987,7 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(), properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(),
Bundle.Accounts_BINNode_binProperty_displayName(), Bundle.Accounts_BINNode_binProperty_displayName(),
Bundle.Accounts_BINNode_noDescription(), Bundle.Accounts_BINNode_noDescription(),
bin.getBIN())); getBinRangeString()));
properties.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(), Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(),
bin.getCount())); bin.getCount()));
@ -1020,6 +1030,8 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
@Override @Override
protected boolean createKeys(List<BinResult> list) { protected boolean createKeys(List<BinResult> list) {
RangeMap<Integer, BinResult> ranges = TreeRangeMap.create();
String query String query
= "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS
+ " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS
@ -1033,9 +1045,24 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) { try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) {
ResultSet resultSet = results.getResultSet(); ResultSet resultSet = results.getResultSet();
while (resultSet.next()) { while (resultSet.next()) {
list.add(new BinResult(Integer.valueOf(resultSet.getString("BIN")), //NON-NLS final Integer bin = Integer.valueOf(resultSet.getString("BIN"));
resultSet.getLong("count"))); //NON-NLS 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) { } catch (TskCoreException | SQLException ex) {
LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS
return false; return false;
@ -1053,76 +1080,92 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
* Data model item to back the BINNodes in the tree. Has the number of * Data model item to back the BINNodes in the tree. Has the number of
* accounts found with the BIN. * accounts found with the BIN.
*/ */
private class BinResult implements IINInfo { @Immutable
static private class BinResult implements IINInfo {
private final Integer bin;
/** /**
* The number of accounts with this BIN * The number of accounts with this BIN
*/ */
private final Long count; private final long count;
private final IINInfo iinInfo;
private BinResult(Integer bin, Long count) { private final IINRange iinRange;
this.bin = bin; private final int iinEnd;
private final int iinStart;
private BinResult(long count, @Nonnull IINRange iinRange) {
this.count = count; this.count = count;
iinInfo = getIINInfo(bin); this.iinRange = iinRange;
iinStart = iinRange.getIINstart();
iinEnd = iinRange.getIINend();
} }
public Integer getBIN() { private BinResult(long count, int start, int end) {
return bin; this.count = count;
this.iinRange = null;
iinStart = start;
iinEnd = end;
} }
public Long getCount() { int getIINStart() {
return iinStart;
}
int getIINEnd() {
return iinEnd;
}
public long getCount() {
return count; return count;
} }
boolean hasDetails() { boolean hasDetails() {
return iinInfo != null; return iinRange != null;
} }
@Override @Override
public Optional<Integer> getNumberLength() { public Optional<Integer> getNumberLength() {
return iinInfo.getNumberLength(); return iinRange.getNumberLength();
} }
@Override @Override
public Optional<String> getBankCity() { public Optional<String> getBankCity() {
return iinInfo.getBankCity(); return iinRange.getBankCity();
} }
@Override @Override
public Optional<String> getBankName() { public Optional<String> getBankName() {
return iinInfo.getBankName(); return iinRange.getBankName();
} }
@Override @Override
public Optional<String> getBankPhoneNumber() { public Optional<String> getBankPhoneNumber() {
return iinInfo.getBankPhoneNumber(); return iinRange.getBankPhoneNumber();
} }
@Override @Override
public Optional<String> getBankURL() { public Optional<String> getBankURL() {
return iinInfo.getBankURL(); return iinRange.getBankURL();
} }
@Override @Override
public Optional<String> getBrand() { public Optional<String> getBrand() {
return iinInfo.getBrand(); return iinRange.getBrand();
} }
@Override @Override
public Optional<String> getCardType() { public Optional<String> getCardType() {
return iinInfo.getCardType(); return iinRange.getCardType();
} }
@Override @Override
public Optional<String> getCountry() { public Optional<String> getCountry() {
return iinInfo.getCountry(); return iinRange.getCountry();
} }
@Override @Override
public Optional<String> getScheme() { public Optional<String> getScheme() {
return iinInfo.getScheme(); return iinRange.getScheme();
} }
} }
@ -1139,13 +1182,14 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
@Override @Override
protected boolean createKeys(List<Long> list) { protected boolean createKeys(List<Long> list) {
String query String query
= "SELECT blackboard_artifacts.artifact_id " //NON-NLS = "SELECT blackboard_artifacts.artifact_id " //NON-NLS
+ " FROM blackboard_artifacts " //NON-NLS + " FROM blackboard_artifacts " //NON-NLS
+ " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //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 + " 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.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER.getTypeID() //NON-NLS
+ " AND blackboard_attributes.value_text LIKE \"" + bin.getBIN() + "%\" " //NON-NLS + " AND blackboard_attributes.value_text >= \"" + bin.getIINStart() + "\" AND blackboard_attributes.value_text < \"" + (bin.getIINEnd() + 1) + "\"" //NON-NLS
+ getRejectedArtifactFilterClause() + getRejectedArtifactFilterClause()
+ " ORDER BY blackboard_attributes.value_text"; //NON-NLS + " ORDER BY blackboard_attributes.value_text"; //NON-NLS
try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
@ -1253,7 +1297,6 @@ public class Accounts extends Observable implements AutopsyVisitableItem {
RejectAccounts(Collection<? extends BlackboardArtifact> artifacts) { RejectAccounts(Collection<? extends BlackboardArtifact> artifacts) {
super(Bundle.RejectAccountsAction_name()); super(Bundle.RejectAccountsAction_name());
this.artifacts = artifacts; this.artifacts = artifacts;
} }

View File

@ -182,21 +182,30 @@ final class TermComponentQuery implements KeywordSearchQuery {
@Override @Override
public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(String termHit, KeywordHit hit, String snippet, String listName) { public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(String termHit, KeywordHit hit, String snippet, String listName) {
BlackboardArtifact newArtifact; BlackboardArtifact newArtifact;
Collection<BlackboardAttribute> attributes = new ArrayList<>(); Collection<BlackboardAttribute> attributes = new ArrayList<>();
try { try {
//if the keyword hit matched the credit card number keyword/regex... //if the keyword hit matched the credit card number keyword/regex...
if (keyword.getType() == ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER) { if (keyword.getType() == ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER) {
termHit = CharMatcher.inRange('0', '9').negate().trimFrom(termHit);
newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT); newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT);
// make account artifact // make account artifact
//try to match it against the track 1 regex //try to match it against the track 1 regex
Matcher matcher = TRACK1_PATTERN.matcher(hit.getSnippet()); Matcher matcher = TRACK1_PATTERN.matcher(hit.getSnippet());
if (matcher.find()) { while (matcher.find()) {
if (termHit.equals(matcher.group("accountNumber"))) {
parseTrack1Data(newArtifact, matcher); parseTrack1Data(newArtifact, matcher);
break;
}
} }
//then try to match it against the track 2 regex //then try to match it against the track 2 regex
matcher = TRACK2_PATTERN.matcher(hit.getSnippet()); matcher = TRACK2_PATTERN.matcher(hit.getSnippet());
if (matcher.find()) { while (matcher.find()) {
final String group = matcher.group("accountNumber");
if (termHit.equals(group)) {
parseTrack2Data(newArtifact, matcher); parseTrack2Data(newArtifact, matcher);
break;
}
} }
if (hit.getContent() instanceof AbstractFile) { if (hit.getContent() instanceof AbstractFile) {
AbstractFile file = (AbstractFile) hit.getContent(); AbstractFile file = (AbstractFile) hit.getContent();