domaintools-api 2.1.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {domaintools_api-2.1.0/domaintools_api.egg-info → domaintools_api-2.3.0}/PKG-INFO +62 -9
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/README.md +59 -1
- domaintools_api-2.3.0/VERSION +1 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/_version.py +1 -1
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/api.py +107 -126
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/base_results.py +74 -58
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/api.py +50 -25
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/commands/domains.py +60 -63
- domaintools_api-2.3.0/domaintools/cli/commands/feeds.py +316 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/constants.py +3 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/utils.py +3 -0
- domaintools_api-2.3.0/domaintools/constants.py +35 -0
- domaintools_api-2.3.0/domaintools/results.py +170 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/utils.py +21 -20
- {domaintools_api-2.1.0 → domaintools_api-2.3.0/domaintools_api.egg-info}/PKG-INFO +62 -9
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools_api.egg-info/SOURCES.txt +1 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools_async/__init__.py +20 -10
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/pyproject.toml +1 -6
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/tests/test_api.py +177 -47
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/tests/test_cli.py +1 -1
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/tests/test_utils.py +54 -52
- domaintools_api-2.1.0/VERSION +0 -1
- domaintools_api-2.1.0/domaintools/cli/commands/feeds.py +0 -138
- domaintools_api-2.1.0/domaintools/results.py +0 -81
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/LICENSE +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/__init__.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/__init__.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/commands/__init__.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/commands/accounts.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/commands/detects.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/commands/ips.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/commands/iris.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/commands/phisheye.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/cli/main.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/exceptions.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools/filters.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools_api.egg-info/dependency_links.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools_api.egg-info/entry_points.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools_api.egg-info/requires.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/domaintools_api.egg-info/top_level.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/setup.cfg +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/setup.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/tests/test_async.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.3.0}/tests/test_filters.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: domaintools_api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: DomainTools Official Python API
|
|
5
5
|
Author-email: DomainTools <integrations@domaintools.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -33,16 +33,11 @@ Classifier: Natural Language :: English
|
|
|
33
33
|
Classifier: Environment :: Console
|
|
34
34
|
Classifier: License :: OSI Approved :: MIT License
|
|
35
35
|
Classifier: Programming Language :: Python
|
|
36
|
-
Classifier: Programming Language :: Python :: 3
|
|
37
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
39
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
40
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
41
36
|
Classifier: Programming Language :: Python :: 3.9
|
|
42
37
|
Classifier: Programming Language :: Python :: 3.10
|
|
43
38
|
Classifier: Topic :: Software Development :: Libraries
|
|
44
39
|
Classifier: Topic :: Utilities
|
|
45
|
-
Requires-Python: >=3.
|
|
40
|
+
Requires-Python: >=3.9
|
|
46
41
|
Description-Content-Type: text/markdown
|
|
47
42
|
License-File: LICENSE
|
|
48
43
|
Requires-Dist: httpx
|
|
@@ -265,5 +260,63 @@ API_KEY
|
|
|
265
260
|
Python Version Support Policy
|
|
266
261
|
===================
|
|
267
262
|
|
|
268
|
-
Please see the [supported versions](https://github.com/DomainTools/python_api/raw/main/PYTHON_SUPPORT.md) document
|
|
263
|
+
Please see the [supported versions](https://github.com/DomainTools/python_api/raw/main/PYTHON_SUPPORT.md) document
|
|
269
264
|
for the DomainTools Python support policy.
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
Real-Time Threat Intelligence Feeds
|
|
268
|
+
===================
|
|
269
|
+
|
|
270
|
+
Real-Time Threat Intelligence Feeds provide data on the different stages of the domain lifecycle: from first-observed in the wild, to newly re-activated after a period of quiet. Access current feed data in real-time or retrieve historical feed data through separate APIs.
|
|
271
|
+
|
|
272
|
+
Custom parameters aside from the common `GET` Request parameters:
|
|
273
|
+
- `endpoint` (choose either `download` or `feed` API endpoint - default is `feed`)
|
|
274
|
+
```python
|
|
275
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
276
|
+
api.nod(endpoint="feed", **kwargs)
|
|
277
|
+
```
|
|
278
|
+
- `header_authentication`: by default, we're using API Header Authentication. Set this False if you want to use API Key and Secret Authentication. Apparently, you can't use API Header Authentication for `download` endpoints so this will be defaulted to `False` even without explicitly setting it.
|
|
279
|
+
```python
|
|
280
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
281
|
+
api.nod(header_authentication=False, **kwargs)
|
|
282
|
+
```
|
|
283
|
+
- `output_format`: (choose either `csv` or `jsonl` - default is `jsonl`). Cannot be used in `domainrdap` feeds. Additionally, `csv` is not available for `download` endpoints.
|
|
284
|
+
```python
|
|
285
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
286
|
+
api.nod(output_format="csv", **kwargs)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The Feed API standard access pattern is to periodically request the most recent feed data, as often as every 60 seconds. Specify the range of data you receive in one of two ways:
|
|
290
|
+
|
|
291
|
+
1. With `sessionID`: Make a call and provide a new `sessionID` parameter of your choosing. The API will return the last hour of data by default.
|
|
292
|
+
- Each subsequent call to the API using your `sessionID` will return all data since the last.
|
|
293
|
+
- Any single request returns a maximum of 10M results. Requests that exceed 10M results will return a HTTP 206 response code; repeat the same request (with the same `sessionID`) to receive the next tranche of data until receiving a HTTP 200 response code.
|
|
294
|
+
2. Or, specify the time range in one of two ways:
|
|
295
|
+
- Either an `after=-60` query parameter, where (in this example) -60 indicates the previous 60 seconds.
|
|
296
|
+
- Or `after` and `before` query parameters for a time range, with each parameter accepting an ISO-8601 UTC formatted timestamp (a UTC date and time of the format YYYY-MM-DDThh:mm:ssZ)
|
|
297
|
+
|
|
298
|
+
## Handling iterative response from RTUF endpoints:
|
|
299
|
+
|
|
300
|
+
Since we may dealing with large feeds datasets, the python wrapper uses `generator` for efficient memory handling. Therefore, we need to iterate through the `generator` if we're accessing the partial results of the feeds data.
|
|
301
|
+
|
|
302
|
+
### Single request because the requested data is within the maximum result:
|
|
303
|
+
```python
|
|
304
|
+
from domaintools import API
|
|
305
|
+
|
|
306
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
307
|
+
results = api.nod(sessionID="my-session-id", after=-60)
|
|
308
|
+
|
|
309
|
+
for result in results.response() # generator that holds NOD feeds data for the past 60 seconds and is expected to request only once
|
|
310
|
+
# do things to result
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Multiple requests because the requested data is more than the maximum result per request:
|
|
314
|
+
```python
|
|
315
|
+
from domaintools import API
|
|
316
|
+
|
|
317
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
318
|
+
results = api.nod(sessionID="my-session-id", after=-7200)
|
|
319
|
+
|
|
320
|
+
for partial_result in results.response() # generator that holds NOD feeds data for the past 2 hours and is expected to request multiple times
|
|
321
|
+
# do things to partial_result
|
|
322
|
+
```
|
|
@@ -211,5 +211,63 @@ API_KEY
|
|
|
211
211
|
Python Version Support Policy
|
|
212
212
|
===================
|
|
213
213
|
|
|
214
|
-
Please see the [supported versions](https://github.com/DomainTools/python_api/raw/main/PYTHON_SUPPORT.md) document
|
|
214
|
+
Please see the [supported versions](https://github.com/DomainTools/python_api/raw/main/PYTHON_SUPPORT.md) document
|
|
215
215
|
for the DomainTools Python support policy.
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
Real-Time Threat Intelligence Feeds
|
|
219
|
+
===================
|
|
220
|
+
|
|
221
|
+
Real-Time Threat Intelligence Feeds provide data on the different stages of the domain lifecycle: from first-observed in the wild, to newly re-activated after a period of quiet. Access current feed data in real-time or retrieve historical feed data through separate APIs.
|
|
222
|
+
|
|
223
|
+
Custom parameters aside from the common `GET` Request parameters:
|
|
224
|
+
- `endpoint` (choose either `download` or `feed` API endpoint - default is `feed`)
|
|
225
|
+
```python
|
|
226
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
227
|
+
api.nod(endpoint="feed", **kwargs)
|
|
228
|
+
```
|
|
229
|
+
- `header_authentication`: by default, we're using API Header Authentication. Set this False if you want to use API Key and Secret Authentication. Apparently, you can't use API Header Authentication for `download` endpoints so this will be defaulted to `False` even without explicitly setting it.
|
|
230
|
+
```python
|
|
231
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
232
|
+
api.nod(header_authentication=False, **kwargs)
|
|
233
|
+
```
|
|
234
|
+
- `output_format`: (choose either `csv` or `jsonl` - default is `jsonl`). Cannot be used in `domainrdap` feeds. Additionally, `csv` is not available for `download` endpoints.
|
|
235
|
+
```python
|
|
236
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
237
|
+
api.nod(output_format="csv", **kwargs)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The Feed API standard access pattern is to periodically request the most recent feed data, as often as every 60 seconds. Specify the range of data you receive in one of two ways:
|
|
241
|
+
|
|
242
|
+
1. With `sessionID`: Make a call and provide a new `sessionID` parameter of your choosing. The API will return the last hour of data by default.
|
|
243
|
+
- Each subsequent call to the API using your `sessionID` will return all data since the last.
|
|
244
|
+
- Any single request returns a maximum of 10M results. Requests that exceed 10M results will return a HTTP 206 response code; repeat the same request (with the same `sessionID`) to receive the next tranche of data until receiving a HTTP 200 response code.
|
|
245
|
+
2. Or, specify the time range in one of two ways:
|
|
246
|
+
- Either an `after=-60` query parameter, where (in this example) -60 indicates the previous 60 seconds.
|
|
247
|
+
- Or `after` and `before` query parameters for a time range, with each parameter accepting an ISO-8601 UTC formatted timestamp (a UTC date and time of the format YYYY-MM-DDThh:mm:ssZ)
|
|
248
|
+
|
|
249
|
+
## Handling iterative response from RTUF endpoints:
|
|
250
|
+
|
|
251
|
+
Since we may dealing with large feeds datasets, the python wrapper uses `generator` for efficient memory handling. Therefore, we need to iterate through the `generator` if we're accessing the partial results of the feeds data.
|
|
252
|
+
|
|
253
|
+
### Single request because the requested data is within the maximum result:
|
|
254
|
+
```python
|
|
255
|
+
from domaintools import API
|
|
256
|
+
|
|
257
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
258
|
+
results = api.nod(sessionID="my-session-id", after=-60)
|
|
259
|
+
|
|
260
|
+
for result in results.response() # generator that holds NOD feeds data for the past 60 seconds and is expected to request only once
|
|
261
|
+
# do things to result
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Multiple requests because the requested data is more than the maximum result per request:
|
|
265
|
+
```python
|
|
266
|
+
from domaintools import API
|
|
267
|
+
|
|
268
|
+
api = API(USERNAME, KEY, always_sign_api_key=False)
|
|
269
|
+
results = api.nod(sessionID="my-session-id", after=-7200)
|
|
270
|
+
|
|
271
|
+
for partial_result in results.response() # generator that holds NOD feeds data for the past 2 hours and is expected to request multiple times
|
|
272
|
+
# do things to partial_result
|
|
273
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.3.0
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
from datetime import datetime, timedelta, timezone
|
|
2
2
|
from hashlib import sha1, sha256, md5
|
|
3
3
|
from hmac import new as hmac
|
|
4
|
+
|
|
4
5
|
import re
|
|
5
6
|
|
|
7
|
+
from domaintools.constants import Endpoint, ENDPOINT_TO_SOURCE_MAP, FEEDS_PRODUCTS_LIST, OutputFormat
|
|
6
8
|
from domaintools._version import current as version
|
|
7
|
-
from domaintools.results import
|
|
9
|
+
from domaintools.results import (
|
|
10
|
+
GroupedIterable,
|
|
11
|
+
ParsedWhois,
|
|
12
|
+
ParsedDomainRdap,
|
|
13
|
+
Reputation,
|
|
14
|
+
Results,
|
|
15
|
+
FeedsResults,
|
|
16
|
+
)
|
|
8
17
|
from domaintools.filters import (
|
|
9
18
|
filter_by_riskscore,
|
|
10
19
|
filter_by_expire_date,
|
|
@@ -12,6 +21,8 @@ from domaintools.filters import (
|
|
|
12
21
|
filter_by_field,
|
|
13
22
|
DTResultFilter,
|
|
14
23
|
)
|
|
24
|
+
from domaintools.utils import validate_feeds_parameters
|
|
25
|
+
|
|
15
26
|
|
|
16
27
|
AVAILABLE_KEY_SIGN_HASHES = ["sha1", "sha256", "md5"]
|
|
17
28
|
|
|
@@ -77,16 +88,9 @@ class API(object):
|
|
|
77
88
|
self._build_api_url(api_url, api_port)
|
|
78
89
|
|
|
79
90
|
if not https:
|
|
80
|
-
raise Exception(
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
if proxy_url:
|
|
84
|
-
if isinstance(proxy_url, str):
|
|
85
|
-
self.proxy_url = {"http://": proxy_url, "https://": proxy_url}
|
|
86
|
-
else:
|
|
87
|
-
raise Exception(
|
|
88
|
-
"Proxy URL must be a string. For example: '127.0.0.1:8888'"
|
|
89
|
-
)
|
|
91
|
+
raise Exception("The DomainTools API endpoints no longer support http traffic. Please make sure https=True.")
|
|
92
|
+
if proxy_url and not isinstance(proxy_url, str):
|
|
93
|
+
raise Exception("Proxy URL must be a string. For example: '127.0.0.1:8888'")
|
|
90
94
|
|
|
91
95
|
def _build_api_url(self, api_url=None, api_port=None):
|
|
92
96
|
"""Build the API url based on the given url and port. Defaults to `https://api.domaintools.com`"""
|
|
@@ -110,51 +114,38 @@ class API(object):
|
|
|
110
114
|
hours = limit_hours and 3600 / float(limit_hours)
|
|
111
115
|
minutes = limit_minutes and 60 / float(limit_minutes)
|
|
112
116
|
|
|
113
|
-
self.limits[product["id"]] = {
|
|
114
|
-
"interval": timedelta(seconds=minutes or hours or default)
|
|
115
|
-
}
|
|
117
|
+
self.limits[product["id"]] = {"interval": timedelta(seconds=minutes or hours or default)}
|
|
116
118
|
|
|
117
119
|
def _results(self, product, path, cls=Results, **kwargs):
|
|
118
120
|
"""Returns _results for the specified API path with the specified **kwargs parameters"""
|
|
119
|
-
if
|
|
120
|
-
product != "account-information"
|
|
121
|
-
and self.rate_limit
|
|
122
|
-
and not self.limits_set
|
|
123
|
-
and not self.limits
|
|
124
|
-
):
|
|
121
|
+
if product != "account-information" and self.rate_limit and not self.limits_set and not self.limits:
|
|
125
122
|
self._rate_limit()
|
|
126
123
|
|
|
127
124
|
uri = "/".join((self._rest_api_url, path.lstrip("/")))
|
|
128
125
|
parameters = self.default_parameters.copy()
|
|
129
126
|
parameters["api_username"] = self.username
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
key: str(value).lower() if value in (True, False) else value
|
|
134
|
-
for key, value in kwargs.items()
|
|
135
|
-
if value is not None
|
|
136
|
-
}
|
|
137
|
-
)
|
|
127
|
+
header_authentication = kwargs.pop("header_authentication", True) # Used only by Real-Time Threat Intelligence Feeds endpoints for now
|
|
128
|
+
self.handle_api_key(product, path, parameters, header_authentication)
|
|
129
|
+
parameters.update({key: str(value).lower() if value in (True, False) else value for key, value in kwargs.items() if value is not None})
|
|
138
130
|
|
|
139
131
|
return cls(self, product, uri, **parameters)
|
|
140
132
|
|
|
141
|
-
def handle_api_key(self, path, parameters):
|
|
133
|
+
def handle_api_key(self, product, path, parameters, header_authentication):
|
|
142
134
|
if self.https and not self.always_sign_api_key:
|
|
143
|
-
|
|
135
|
+
if product in FEEDS_PRODUCTS_LIST and header_authentication:
|
|
136
|
+
parameters["X-Api-Key"] = self.key
|
|
137
|
+
else:
|
|
138
|
+
parameters["api_key"] = self.key
|
|
144
139
|
else:
|
|
145
140
|
if self.key_sign_hash and self.key_sign_hash in AVAILABLE_KEY_SIGN_HASHES:
|
|
146
141
|
signing_hash = eval(self.key_sign_hash)
|
|
147
142
|
else:
|
|
148
143
|
raise ValueError(
|
|
149
144
|
"Invalid value '{0}' for 'key_sign_hash'. "
|
|
150
|
-
"Values available are {1}".format(
|
|
151
|
-
self.key_sign_hash, ",".join(AVAILABLE_KEY_SIGN_HASHES)
|
|
152
|
-
)
|
|
145
|
+
"Values available are {1}".format(self.key_sign_hash, ",".join(AVAILABLE_KEY_SIGN_HASHES))
|
|
153
146
|
)
|
|
154
147
|
|
|
155
|
-
parameters["timestamp"] = datetime.now(timezone.utc).strftime(
|
|
156
|
-
"%Y-%m-%dT%H:%M:%SZ"
|
|
157
|
-
)
|
|
148
|
+
parameters["timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
158
149
|
parameters["signature"] = hmac(
|
|
159
150
|
self.key.encode("utf8"),
|
|
160
151
|
"".join([self.username, parameters["timestamp"], path]).encode("utf8"),
|
|
@@ -163,9 +154,7 @@ class API(object):
|
|
|
163
154
|
|
|
164
155
|
def account_information(self, **kwargs):
|
|
165
156
|
"""Provides a snapshot of your accounts current API usage"""
|
|
166
|
-
return self._results(
|
|
167
|
-
"account-information", "/v1/account", items_path=("products",), **kwargs
|
|
168
|
-
)
|
|
157
|
+
return self._results("account-information", "/v1/account", items_path=("products",), **kwargs)
|
|
169
158
|
|
|
170
159
|
def available_api_calls(self):
|
|
171
160
|
"""Provides a list of api calls that you can use based on your account information."""
|
|
@@ -180,25 +169,10 @@ class API(object):
|
|
|
180
169
|
string[1:],
|
|
181
170
|
)
|
|
182
171
|
|
|
183
|
-
api_calls = tuple(
|
|
184
|
-
|
|
185
|
-
api_call
|
|
186
|
-
for api_call in dir(API)
|
|
187
|
-
if not api_call.startswith("_")
|
|
188
|
-
and callable(getattr(API, api_call, None))
|
|
189
|
-
)
|
|
190
|
-
)
|
|
191
|
-
return sorted(
|
|
192
|
-
[
|
|
193
|
-
snakecase(p["id"])
|
|
194
|
-
for p in self.account_information()["products"]
|
|
195
|
-
if snakecase(p["id"]) in api_calls
|
|
196
|
-
]
|
|
197
|
-
)
|
|
172
|
+
api_calls = tuple((api_call for api_call in dir(API) if not api_call.startswith("_") and callable(getattr(API, api_call, None))))
|
|
173
|
+
return sorted([snakecase(p["id"]) for p in self.account_information()["products"] if snakecase(p["id"]) in api_calls])
|
|
198
174
|
|
|
199
|
-
def brand_monitor(
|
|
200
|
-
self, query, exclude=None, domain_status=None, days_back=None, **kwargs
|
|
201
|
-
):
|
|
175
|
+
def brand_monitor(self, query, exclude=None, domain_status=None, days_back=None, **kwargs):
|
|
202
176
|
"""Pass in one or more terms as a list or separated by the pipe character ( | )"""
|
|
203
177
|
if exclude is None:
|
|
204
178
|
exclude = []
|
|
@@ -324,9 +298,16 @@ class API(object):
|
|
|
324
298
|
**kwargs,
|
|
325
299
|
)
|
|
326
300
|
|
|
327
|
-
def
|
|
328
|
-
|
|
329
|
-
|
|
301
|
+
def parsed_domain_rdap(self, query, **kwargs):
|
|
302
|
+
"""Pass in a domain name to see the most recent Domain-RDAP registration record"""
|
|
303
|
+
return self._results(
|
|
304
|
+
"parsed-domain-rdap",
|
|
305
|
+
"/v1/{0}/rdap/parsed/".format(query),
|
|
306
|
+
cls=ParsedDomainRdap,
|
|
307
|
+
**kwargs,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
def registrant_monitor(self, query, exclude=None, days_back=0, limit=None, **kwargs):
|
|
330
311
|
"""One or more terms as a Python list or separated by the pipe character ( | )."""
|
|
331
312
|
if exclude is None:
|
|
332
313
|
exclude = []
|
|
@@ -354,15 +335,11 @@ class API(object):
|
|
|
354
335
|
|
|
355
336
|
def reverse_ip(self, domain=None, limit=None, **kwargs):
|
|
356
337
|
"""Pass in a domain name."""
|
|
357
|
-
return self._results(
|
|
358
|
-
"reverse-ip", "/v1/{0}/reverse-ip".format(domain), limit=limit, **kwargs
|
|
359
|
-
)
|
|
338
|
+
return self._results("reverse-ip", "/v1/{0}/reverse-ip".format(domain), limit=limit, **kwargs)
|
|
360
339
|
|
|
361
340
|
def host_domains(self, ip=None, limit=None, **kwargs):
|
|
362
341
|
"""Pass in an IP address."""
|
|
363
|
-
return self._results(
|
|
364
|
-
"reverse-ip", "/v1/{0}/host-domains".format(ip), limit=limit, **kwargs
|
|
365
|
-
)
|
|
342
|
+
return self._results("reverse-ip", "/v1/{0}/host-domains".format(ip), limit=limit, **kwargs)
|
|
366
343
|
|
|
367
344
|
def reverse_ip_whois(
|
|
368
345
|
self,
|
|
@@ -401,9 +378,7 @@ class API(object):
|
|
|
401
378
|
**kwargs,
|
|
402
379
|
)
|
|
403
380
|
|
|
404
|
-
def reverse_whois(
|
|
405
|
-
self, query, exclude=None, scope="current", mode="purchase", **kwargs
|
|
406
|
-
):
|
|
381
|
+
def reverse_whois(self, query, exclude=None, scope="current", mode="purchase", **kwargs):
|
|
407
382
|
"""List of one or more terms to search for in the Whois record,
|
|
408
383
|
as a Python list or separated with the pipe character ( | ).
|
|
409
384
|
"""
|
|
@@ -423,9 +398,7 @@ class API(object):
|
|
|
423
398
|
"""Pass in a domain name or an IP address to perform a whois lookup."""
|
|
424
399
|
return self._results("whois", "/v1/{0}/whois".format(query), **kwargs)
|
|
425
400
|
|
|
426
|
-
def whois_history(
|
|
427
|
-
self, query, mode=None, sort=None, offset=None, limit=None, **kwargs
|
|
428
|
-
):
|
|
401
|
+
def whois_history(self, query, mode=None, sort=None, offset=None, limit=None, **kwargs):
|
|
429
402
|
"""Pass in a domain name."""
|
|
430
403
|
return self._results(
|
|
431
404
|
"whois-history",
|
|
@@ -484,16 +457,7 @@ class API(object):
|
|
|
484
457
|
"""Performs a search for the provided search terms ANDed together,
|
|
485
458
|
returning the pivot engine row data for the resulting domains.
|
|
486
459
|
"""
|
|
487
|
-
if
|
|
488
|
-
not domain
|
|
489
|
-
and not ip
|
|
490
|
-
and not email
|
|
491
|
-
and not nameserver
|
|
492
|
-
and not registrar
|
|
493
|
-
and not registrant
|
|
494
|
-
and not registrant_org
|
|
495
|
-
and not kwargs
|
|
496
|
-
):
|
|
460
|
+
if not domain and not ip and not email and not nameserver and not registrar and not registrant and not registrant_org and not kwargs:
|
|
497
461
|
raise ValueError("At least one search term must be specified")
|
|
498
462
|
|
|
499
463
|
return self._results(
|
|
@@ -568,12 +532,8 @@ class API(object):
|
|
|
568
532
|
younger_than_date = kwargs.pop("younger_than_date", {}) or None
|
|
569
533
|
older_than_date = kwargs.pop("older_than_date", {}) or None
|
|
570
534
|
updated_after = kwargs.pop("updated_after", {}) or None
|
|
571
|
-
include_domains_with_missing_field = (
|
|
572
|
-
|
|
573
|
-
)
|
|
574
|
-
exclude_domains_with_missing_field = (
|
|
575
|
-
kwargs.pop("exclude_domains_with_missing_field", {}) or None
|
|
576
|
-
)
|
|
535
|
+
include_domains_with_missing_field = kwargs.pop("include_domains_with_missing_field", {}) or None
|
|
536
|
+
exclude_domains_with_missing_field = kwargs.pop("exclude_domains_with_missing_field", {}) or None
|
|
577
537
|
|
|
578
538
|
filtered_results = DTResultFilter(result_set=results).by(
|
|
579
539
|
[
|
|
@@ -581,12 +541,8 @@ class API(object):
|
|
|
581
541
|
filter_by_expire_date(date=younger_than_date, lookup_type="before"),
|
|
582
542
|
filter_by_expire_date(date=older_than_date, lookup_type="after"),
|
|
583
543
|
filter_by_date_updated_after(date=updated_after),
|
|
584
|
-
filter_by_field(
|
|
585
|
-
|
|
586
|
-
),
|
|
587
|
-
filter_by_field(
|
|
588
|
-
field=exclude_domains_with_missing_field, filter_type="exclude"
|
|
589
|
-
),
|
|
544
|
+
filter_by_field(field=include_domains_with_missing_field, filter_type="include"),
|
|
545
|
+
filter_by_field(field=exclude_domains_with_missing_field, filter_type="exclude"),
|
|
590
546
|
]
|
|
591
547
|
)
|
|
592
548
|
|
|
@@ -691,9 +647,7 @@ class API(object):
|
|
|
691
647
|
kwargs["search_hash"] = search_hash
|
|
692
648
|
|
|
693
649
|
if not (kwargs or domains):
|
|
694
|
-
raise ValueError(
|
|
695
|
-
"Need to define investigation using kwarg filters or domains"
|
|
696
|
-
)
|
|
650
|
+
raise ValueError("Need to define investigation using kwarg filters or domains")
|
|
697
651
|
|
|
698
652
|
if isinstance(domains, (list, tuple)):
|
|
699
653
|
domains = ",".join(domains)
|
|
@@ -723,12 +677,8 @@ class API(object):
|
|
|
723
677
|
filter_by_expire_date(date=younger_than_date, lookup_type="before"),
|
|
724
678
|
filter_by_expire_date(date=older_than_date, lookup_type="after"),
|
|
725
679
|
filter_by_date_updated_after(date=updated_after),
|
|
726
|
-
filter_by_field(
|
|
727
|
-
|
|
728
|
-
),
|
|
729
|
-
filter_by_field(
|
|
730
|
-
field=exclude_domains_with_missing_field, filter_type="exclude"
|
|
731
|
-
),
|
|
680
|
+
filter_by_field(field=include_domains_with_missing_field, filter_type="include"),
|
|
681
|
+
filter_by_field(field=exclude_domains_with_missing_field, filter_type="exclude"),
|
|
732
682
|
]
|
|
733
683
|
)
|
|
734
684
|
|
|
@@ -768,9 +718,7 @@ class API(object):
|
|
|
768
718
|
|
|
769
719
|
if include_counts:
|
|
770
720
|
if not datetime_counts_since:
|
|
771
|
-
raise ValueError(
|
|
772
|
-
"Need to define datetime_counts_since when include_counts is True"
|
|
773
|
-
)
|
|
721
|
+
raise ValueError("Need to define datetime_counts_since when include_counts is True")
|
|
774
722
|
if isinstance(datetime_counts_since, datetime):
|
|
775
723
|
datetime_counts_since = str(datetime_counts_since.astimezone())
|
|
776
724
|
elif isinstance(datetime_counts_since, str):
|
|
@@ -978,9 +926,7 @@ class API(object):
|
|
|
978
926
|
**kwargs,
|
|
979
927
|
)
|
|
980
928
|
|
|
981
|
-
def iris_detect_manage_watchlist_domains(
|
|
982
|
-
self, watchlist_domain_ids, state, **kwargs
|
|
983
|
-
):
|
|
929
|
+
def iris_detect_manage_watchlist_domains(self, watchlist_domain_ids, state, **kwargs):
|
|
984
930
|
"""Changes the watch state of a list of domains by their Iris Detect domain ID.
|
|
985
931
|
|
|
986
932
|
watchlist_domain_ids: List[str]: required. List of Iris Detect domain IDs to manage.
|
|
@@ -999,9 +945,7 @@ class API(object):
|
|
|
999
945
|
**kwargs,
|
|
1000
946
|
)
|
|
1001
947
|
|
|
1002
|
-
def iris_detect_escalate_domains(
|
|
1003
|
-
self, watchlist_domain_ids, escalation_type, **kwargs
|
|
1004
|
-
):
|
|
948
|
+
def iris_detect_escalate_domains(self, watchlist_domain_ids, escalation_type, **kwargs):
|
|
1005
949
|
"""Changes the escalation type of a list of domains by their Iris Detect domain ID.
|
|
1006
950
|
|
|
1007
951
|
watchlist_domain_ids: List[str]: required. List of Iris Detect domain IDs to escalate.
|
|
@@ -1120,30 +1064,67 @@ class API(object):
|
|
|
1120
1064
|
**kwargs,
|
|
1121
1065
|
)
|
|
1122
1066
|
|
|
1123
|
-
def nod(self, **kwargs):
|
|
1067
|
+
def nod(self, **kwargs) -> FeedsResults:
|
|
1124
1068
|
"""Returns back list of the newly observed domains feed"""
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1069
|
+
validate_feeds_parameters(kwargs)
|
|
1070
|
+
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
|
|
1071
|
+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint)
|
|
1072
|
+
if endpoint == Endpoint.DOWNLOAD.value or kwargs.get("output_format", OutputFormat.JSONL.value) != OutputFormat.CSV.value:
|
|
1073
|
+
# headers param is allowed only in Feed API and CSV format
|
|
1074
|
+
kwargs.pop("headers", None)
|
|
1129
1075
|
|
|
1130
1076
|
return self._results(
|
|
1131
|
-
"newly-observed-domains-feed-(
|
|
1132
|
-
"v1/
|
|
1077
|
+
f"newly-observed-domains-feed-({source.value})",
|
|
1078
|
+
f"v1/{endpoint}/nod/",
|
|
1133
1079
|
response_path=(),
|
|
1080
|
+
cls=FeedsResults,
|
|
1134
1081
|
**kwargs,
|
|
1135
1082
|
)
|
|
1136
1083
|
|
|
1137
|
-
def nad(self, **kwargs):
|
|
1084
|
+
def nad(self, **kwargs) -> FeedsResults:
|
|
1138
1085
|
"""Returns back list of the newly active domains feed"""
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1086
|
+
validate_feeds_parameters(kwargs)
|
|
1087
|
+
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
|
|
1088
|
+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint).value
|
|
1089
|
+
if endpoint == Endpoint.DOWNLOAD.value or kwargs.get("output_format", OutputFormat.JSONL.value) != OutputFormat.CSV.value:
|
|
1090
|
+
# headers param is allowed only in Feed API and CSV format
|
|
1091
|
+
kwargs.pop("headers", None)
|
|
1092
|
+
|
|
1093
|
+
return self._results(
|
|
1094
|
+
f"newly-active-domains-feed-({source})",
|
|
1095
|
+
f"v1/{endpoint}/nad/",
|
|
1096
|
+
response_path=(),
|
|
1097
|
+
cls=FeedsResults,
|
|
1098
|
+
**kwargs,
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1101
|
+
def domainrdap(self, **kwargs) -> FeedsResults:
|
|
1102
|
+
"""Returns changes to global domain registration information, populated by the Registration Data Access Protocol (RDAP)"""
|
|
1103
|
+
validate_feeds_parameters(kwargs)
|
|
1104
|
+
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
|
|
1105
|
+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint).value
|
|
1106
|
+
|
|
1107
|
+
return self._results(
|
|
1108
|
+
f"domain-registration-data-access-protocol-feed-({source})",
|
|
1109
|
+
f"v1/{endpoint}/domainrdap/",
|
|
1110
|
+
response_path=(),
|
|
1111
|
+
cls=FeedsResults,
|
|
1112
|
+
**kwargs,
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
def domaindiscovery(self, **kwargs) -> FeedsResults:
|
|
1116
|
+
"""Returns new domains as they are either discovered in domain registration information, observed by our global sensor network, or reported by trusted third parties"""
|
|
1117
|
+
validate_feeds_parameters(kwargs)
|
|
1118
|
+
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
|
|
1119
|
+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint).value
|
|
1120
|
+
if endpoint == Endpoint.DOWNLOAD.value or kwargs.get("output_format", OutputFormat.JSONL.value) != OutputFormat.CSV.value:
|
|
1121
|
+
# headers param is allowed only in Feed API and CSV format
|
|
1122
|
+
kwargs.pop("headers", None)
|
|
1143
1123
|
|
|
1144
1124
|
return self._results(
|
|
1145
|
-
"
|
|
1146
|
-
"v1/
|
|
1125
|
+
f"real-time-domain-discovery-feed-({source})",
|
|
1126
|
+
f"v1/{endpoint}/domaindiscovery/",
|
|
1147
1127
|
response_path=(),
|
|
1128
|
+
cls=FeedsResults,
|
|
1148
1129
|
**kwargs,
|
|
1149
1130
|
)
|