Skip to content

Commit aa39067

Browse files
authored
Merge pull request #27 from passivetotal/v2.4
V2.4.0
2 parents 2eabba5 + ce6913f commit aa39067

21 files changed

+782
-179
lines changed

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Changelog
2+
3+
## v2.4.0
4+
5+
#### Enhancements:
6+
7+
- Early implementation of exception handling for SSL properties; analyzer.
8+
AnalyzerError now available as a base exception type.
9+
- SSL certs will now populate their own `ip` property, accessing the
10+
SSL history API when needed to fill in the details.
11+
- New `iphistory` property of SSL certs to support the `ip` property and
12+
give direct access to the historial results.
13+
- Used the `tldextract` Python library to expose useful properties on Hostname
14+
objects such as `tld`, `registered_domain`, and `subdomain`
15+
- Change default days back for date-aware searches to 90 days (was 30)
16+
- Reject IPs as strings for Hostname objects
17+
- Ensure IPs are used when instantiating IPAddress objects
18+
- Defang hostnames (i.e. `analyzer.Hostname('api[.]riskiq[.]net')` )
19+
- Support for Articles as a property of Hostnames and IPs, with autoloading
20+
for detailed fields including indicators, plus easy access to a list of all
21+
articles directly from `analyzer.AllArticles()`
22+
- Support for Malware as a property of Hostnames and IPs
23+
- Better coverage of pretty printing and dictionary representation across
24+
analyzer objects.
25+
26+
27+
#### Bug Fixes:
28+
29+
- Exception handling when no details found for an SSL certificate.
30+
- Proper handling of None types that may have prevented result caching
31+
32+
---

passivetotal/analyzer/__init__.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from collections import namedtuple
44
from datetime import datetime, timezone, timedelta
55
from passivetotal import *
6+
from passivetotal.analyzer._common import AnalyzerError, is_ip
67

7-
DEFAULT_DAYS_BACK = 30
8+
DEFAULT_DAYS_BACK = 90
89

910
api_clients = {}
1011
config = {
@@ -67,6 +68,23 @@ def get_config(key=None):
6768
return config[key]
6869
return config
6970

71+
def get_object(input, type=None):
72+
"""Get an Analyzer object for a given input and type. If no type is specified,
73+
type will be autodetected based on the input.
74+
75+
Returns :class:`analyzer.Hostname` or :class:`analyzer.IPAddress`.
76+
"""
77+
objs = {
78+
'IPAddress': IPAddress,
79+
'Hostname': Hostname
80+
}
81+
if type is None:
82+
type = 'IPAddress' if is_ip(input) else 'Hostname'
83+
elif type not in objs.keys():
84+
raise AnalyzerError('type must be IPAddress or Hostname')
85+
return objs[type](input)
86+
87+
7088
def set_date_range(days_back=DEFAULT_DAYS_BACK, start=None, end=None):
7189
"""Set a range of dates for all date-bounded API queries.
7290
@@ -135,4 +153,5 @@ def set_dateorder_descending():
135153

136154
from passivetotal.analyzer.hostname import Hostname
137155
from passivetotal.analyzer.ip import IPAddress
138-
from passivetotal.analyzer.ssl import CertificateField
156+
from passivetotal.analyzer.ssl import CertificateField
157+
from passivetotal.analyzer.articles import AllArticles

passivetotal/analyzer/_common.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
"""Base classes and common methods for the analyzer package."""
2-
2+
import pprint
33

44

55
from datetime import datetime
6+
import re
7+
68

9+
def is_ip(test):
10+
"""Test to see if a string contains an IPv4 address."""
11+
pattern = re.compile(r"(\d{1,3}(?:\.|\]\.\[|\[\.\]|\(\.\)|{\.})\d{1,3}(?:\.|\]\.\[|\[\.\]|\(\.\)|{\.})\d{1,3}(?:\.|\]\.\[|\[\.\]|\(\.\)|{\.})\d{1,3})")
12+
return len(pattern.findall(test)) > 0
713

14+
def refang(hostname):
15+
"""Remove square braces around dots in a hostname."""
16+
return re.sub(r'[\[\]]','', hostname)
817

918
class RecordList:
1019

@@ -221,3 +230,40 @@ def has_more_records(self):
221230
"""
222231
return len(self) < self._totalrecords
223232

233+
234+
class PrettyRecord:
235+
"""A record that can pretty-print itself.
236+
237+
For best results, wrap this property in a print() statement.
238+
239+
Depends on a as_dict property on the base object.
240+
"""
241+
242+
@property
243+
def pretty(self):
244+
"""Pretty printed version of this record."""
245+
from passivetotal.analyzer import get_config
246+
config = get_config('pprint')
247+
return pprint.pformat(self.as_dict, **config)
248+
249+
250+
251+
class PrettyList:
252+
"""A record list that can pretty-print itself.
253+
254+
Depends on an as_dict property each object in the list.
255+
"""
256+
257+
@property
258+
def pretty(self):
259+
"""Pretty printed version of this record list."""
260+
from passivetotal.analyzer import get_config
261+
config = get_config('pprint')
262+
return pprint.pformat([r.as_dict for r in self], **config)
263+
264+
265+
266+
267+
class AnalyzerError(Exception):
268+
"""Base error class for Analyzer objects."""
269+
pass

0 commit comments

Comments
 (0)