diff --git a/spiderfoot/target.py b/spiderfoot/target.py index 6f028814e4..3f6a2b088a 100644 --- a/spiderfoot/target.py +++ b/spiderfoot/target.py @@ -1,214 +1,57 @@ import netaddr - -class SpiderFootTarget(): - """SpiderFoot target. - - Attributes: - validTypes (list): valid event types accepted as a target - targetType (str): target type - targetValue (str): target value - targetAliases (list): target aliases - """ - - _validTypes = ["IP_ADDRESS", 'IPV6_ADDRESS', "NETBLOCK_OWNER", "NETBLOCKV6_OWNER", "INTERNET_NAME", +class SpiderFootTarget: + _valid_types = ["IP_ADDRESS", 'IPV6_ADDRESS', "NETBLOCK_OWNER", "NETBLOCKV6_OWNER", "INTERNET_NAME", "EMAILADDR", "HUMAN_NAME", "BGP_AS_OWNER", 'PHONE_NUMBER', "USERNAME", "BITCOIN_ADDRESS"] - _targetType = None - _targetValue = None - _targetAliases = list() - - def __init__(self, targetValue: str, typeName: str) -> None: - """Initialize SpiderFoot target. - - Args: - targetValue (str): target value - typeName (str): target type - """ - self.targetType = typeName - self.targetValue = targetValue - self.targetAliases = list() - - @property - def targetType(self) -> str: - return self._targetType - - @targetType.setter - def targetType(self, targetType: str) -> None: - if not isinstance(targetType, str): - raise TypeError(f"targetType is {type(targetType)}; expected str()") - - if targetType not in self._validTypes: - raise ValueError(f"targetType value is {targetType}; expected {self._validTypes}") - self._targetType = targetType + def __init__(self, target_value: str, type_name: str) -> None: + self.target_type = type_name + self.target_value = target_value + self.aliases = [] @property - def targetValue(self) -> str: - return self._targetValue + def target_type(self) -> str: + return self._target_type - @targetValue.setter - def targetValue(self, targetValue: str) -> None: - if not isinstance(targetValue, str): - raise TypeError(f"targetValue is {type(targetValue)}; expected str()") - if not targetValue: - raise ValueError("targetValue value is blank") - - self._targetValue = targetValue + @target_type.setter + def target_type(self, target_type: str) -> None: + if target_type not in self._valid_types: + raise ValueError(f"Invalid target type: {target_type}. Expected {self._valid_types}") + self._target_type = target_type @property - def targetAliases(self) -> list: - return self._targetAliases - - @targetAliases.setter - def targetAliases(self, value: list) -> None: - self._targetAliases = value - - def setAlias(self, value: str, typeName: str) -> None: - """Specify other hostnames, IPs, etc. that are aliases for this target. + def target_value(self) -> str: + return self._target_value - For instance, if the user searched for an ASN, a module - might supply all the nested subnets as aliases. - Or, if a user searched for an IP address, a module - might supply the hostname as an alias. + @target_value.setter + def target_value(self, target_value: str) -> None: + if not target_value: + raise ValueError("Target value cannot be empty") + self._target_value = target_value - Args: - value (str): Target alias value - typeName (str): Target alias data type - """ - if not isinstance(value, (str, bytes)): + def set_alias(self, value: str, type_name: str) -> None: + if not value or not type_name: return - - if not value: - return - - if not isinstance(typeName, (str, bytes)): + alias = {'type': type_name, 'value': value.lower()} + if alias in self.aliases: return + self.aliases.append(alias) - if not typeName: - return - - alias = {'type': typeName, 'value': value.lower()} - - if alias in self.targetAliases: - return - - self.targetAliases.append(alias) - - def _getEquivalents(self, typeName: str) -> list: - """Get all aliases of the specfied target data type. - - Args: - typeName (str): Target data type - - Returns: - list: target aliases - """ - ret = list() - for item in self.targetAliases: - if item['type'] == typeName: - ret.append(item['value'].lower()) - return ret - - def getNames(self) -> list: - """Get all domains associated with the target. - - Returns: - list: domains associated with the target - """ - e = self._getEquivalents("INTERNET_NAME") - if self.targetType in ["INTERNET_NAME", "EMAILADDR"] and self.targetValue.lower() not in e: - e.append(self.targetValue.lower()) - - names = list() - for name in e: - if isinstance(name, bytes): - names.append(name.decode("utf-8")) - else: - names.append(name) + def _get_equivalents(self, type_name: str) -> list: + return [item['value'].lower() for item in self.aliases if item['type'] == type_name] + def get_names(self) -> list: + names = self._get_equivalents("INTERNET_NAME") + if self.target_type in ["INTERNET_NAME", "EMAILADDR"] and self.target_value.lower() not in names: + names.append(self.target_value.lower()) return names - def getAddresses(self) -> list: - """Get all IP subnet or IP address aliases associated with the target. - - Returns: - list: List of IP subnets and addresses - """ - e = self._getEquivalents("IP_ADDRESS") - if self.targetType == "IP_ADDRESS": - e.append(self.targetValue) - - e = self._getEquivalents("IPV6_ADDRESS") - if self.targetType == "IPV6_ADDRESS": - e.append(self.targetValue) - - return e - - def matches(self, value: str, includeParents: bool = False, includeChildren: bool = True) -> bool: - """Check whether the supplied value is "tightly" related to the original target. - - Tightly in this case means: - - If the value is an IP: - * is it in the list of aliases or the target itself? - * is it on the target's subnet? - - If the value is an internet name (subdomain, domain, hostname): - * is it in the list of aliases or the target itself? - * is it a parent of the aliases of the target (domain/subdomain) - * is it a child of the aliases of the target (hostname) - - Args: - value (str): can be an Internet Name (hostname, subnet, domain) or an IP address. - includeParents (bool): True means you consider a value that is - a parent domain of the target to still be a tight relation. - includeChildren (bool): False means you don't consider a value - that is a child of the target to be a tight relation. - - Returns: - bool: whether the value matches the target - """ - if not isinstance(value, str) and not isinstance(value, bytes): - return False - - if isinstance(value, bytes): - value = value.decode("utf-8") - - if not value: - return False - - # We can't really say anything about names, username, bitcoin addresses - # or phone numbers, so everything matches - if self.targetType in ["HUMAN_NAME", "PHONE_NUMBER", "USERNAME", "BITCOIN_ADDRESS"]: - return True - - # TODO: review handling of other potential self.targetType target types: - # "INTERNET_NAME", "EMAILADDR", "BGP_AS_OWNER" - - # For IP addreses, check if it is an alias of the target or within the target's subnet. - if netaddr.valid_ipv4(value) or netaddr.valid_ipv6(value): - if value in self.getAddresses(): - return True - - if self.targetType in ["IP_ADDRESS", "IPV6_ADDRESS", "NETBLOCK_OWNER", "NETBLOCKV6_OWNER"]: - try: - if netaddr.IPAddress(value) in netaddr.IPNetwork(self.targetValue): - return True - except netaddr.AddrFormatError: - return False - - return False - - # For everything else, check if the value is within or equal to target names - for name in self.getNames(): - if value == name: - return True - if includeParents and name.endswith("." + value): - return True - if includeChildren and value.endswith("." + name): - return True - - return False - -# end of SpiderFootTarget class + def get_addresses(self) -> list: + addresses = self._get_equivalents("IP_ADDRESS") + if self.target_type == "IP_ADDRESS": + addresses.append(self.target_value) + addresses.extend(self._get_equivalents("IPV6_ADDRESS")) + if self.target_type == "IPV6_ADDRESS": + addresses.append(self.target_value) + return addresses