domaintools-api 2.5.2__tar.gz → 2.5.3__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.5.2/domaintools_api.egg-info → domaintools_api-2.5.3}/PKG-INFO +9 -9
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/README.md +8 -8
- domaintools_api-2.5.3/VERSION +1 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/_version.py +1 -1
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/api.py +24 -9
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/base_results.py +23 -23
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/api.py +5 -3
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/commands/feeds.py +14 -14
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/commands/iris.py +24 -27
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/constants.py +1 -1
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/utils.py +0 -4
- {domaintools_api-2.5.2 → domaintools_api-2.5.3/domaintools_api.egg-info}/PKG-INFO +9 -9
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools_async/__init__.py +7 -9
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/tests/test_api.py +26 -12
- domaintools_api-2.5.3/tests/test_async.py +50 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/tests/test_utils.py +0 -11
- domaintools_api-2.5.2/VERSION +0 -1
- domaintools_api-2.5.2/tests/test_async.py +0 -64
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/LICENSE +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/__init__.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/__init__.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/commands/__init__.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/commands/accounts.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/commands/detects.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/commands/domains.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/commands/ips.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/constants.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/main.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/cli/utils.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/exceptions.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/filters.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools/results.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools_api.egg-info/SOURCES.txt +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools_api.egg-info/dependency_links.txt +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools_api.egg-info/entry_points.txt +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools_api.egg-info/requires.txt +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools_api.egg-info/top_level.txt +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/pyproject.toml +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/setup.cfg +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/setup.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/tests/test_cli.py +0 -0
- {domaintools_api-2.5.2 → domaintools_api-2.5.3}/tests/test_filters.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: domaintools_api
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
4
4
|
Summary: DomainTools Official Python API
|
|
5
5
|
Author-email: DomainTools <integrations@domaintools.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -265,25 +265,25 @@ Please see the [supported versions](https://github.com/DomainTools/python_api/ra
|
|
|
265
265
|
for the DomainTools Python support policy.
|
|
266
266
|
|
|
267
267
|
|
|
268
|
-
Real-Time Threat
|
|
268
|
+
Real-Time Threat Feeds
|
|
269
269
|
===================
|
|
270
270
|
|
|
271
|
-
Real-Time Threat
|
|
271
|
+
Real-Time Threat 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.
|
|
272
272
|
|
|
273
273
|
Custom parameters aside from the common `GET` Request parameters:
|
|
274
274
|
- `endpoint` (choose either `download` or `feed` API endpoint - default is `feed`)
|
|
275
275
|
```python
|
|
276
|
-
api = API(USERNAME, KEY
|
|
276
|
+
api = API(USERNAME, KEY)
|
|
277
277
|
api.nod(endpoint="feed", **kwargs)
|
|
278
278
|
```
|
|
279
279
|
- `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.
|
|
280
280
|
```python
|
|
281
|
-
api = API(USERNAME, KEY,
|
|
282
|
-
api.nod(
|
|
281
|
+
api = API(USERNAME, KEY, header_authentication=False)
|
|
282
|
+
api.nod(**kwargs)
|
|
283
283
|
```
|
|
284
284
|
- `output_format`: (choose either `csv` or `jsonl` - default is `jsonl`). Cannot be used in `domainrdap` feeds. Additionally, `csv` is not available for `download` endpoints.
|
|
285
285
|
```python
|
|
286
|
-
api = API(USERNAME, KEY
|
|
286
|
+
api = API(USERNAME, KEY)
|
|
287
287
|
api.nod(output_format="csv", **kwargs)
|
|
288
288
|
```
|
|
289
289
|
|
|
@@ -304,7 +304,7 @@ Since we may dealing with large feeds datasets, the python wrapper uses `generat
|
|
|
304
304
|
```python
|
|
305
305
|
from domaintools import API
|
|
306
306
|
|
|
307
|
-
api = API(USERNAME, KEY
|
|
307
|
+
api = API(USERNAME, KEY)
|
|
308
308
|
results = api.nod(sessionID="my-session-id", after=-60)
|
|
309
309
|
|
|
310
310
|
for result in results.response() # generator that holds NOD feeds data for the past 60 seconds and is expected to request only once
|
|
@@ -315,7 +315,7 @@ for result in results.response() # generator that holds NOD feeds data for the p
|
|
|
315
315
|
```python
|
|
316
316
|
from domaintools import API
|
|
317
317
|
|
|
318
|
-
api = API(USERNAME, KEY
|
|
318
|
+
api = API(USERNAME, KEY)
|
|
319
319
|
results = api.nod(sessionID="my-session-id", after=-7200)
|
|
320
320
|
|
|
321
321
|
for partial_result in results.response() # generator that holds NOD feeds data for the past 2 hours and is expected to request multiple times
|
|
@@ -215,25 +215,25 @@ Please see the [supported versions](https://github.com/DomainTools/python_api/ra
|
|
|
215
215
|
for the DomainTools Python support policy.
|
|
216
216
|
|
|
217
217
|
|
|
218
|
-
Real-Time Threat
|
|
218
|
+
Real-Time Threat Feeds
|
|
219
219
|
===================
|
|
220
220
|
|
|
221
|
-
Real-Time Threat
|
|
221
|
+
Real-Time Threat 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
222
|
|
|
223
223
|
Custom parameters aside from the common `GET` Request parameters:
|
|
224
224
|
- `endpoint` (choose either `download` or `feed` API endpoint - default is `feed`)
|
|
225
225
|
```python
|
|
226
|
-
api = API(USERNAME, KEY
|
|
226
|
+
api = API(USERNAME, KEY)
|
|
227
227
|
api.nod(endpoint="feed", **kwargs)
|
|
228
228
|
```
|
|
229
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
230
|
```python
|
|
231
|
-
api = API(USERNAME, KEY,
|
|
232
|
-
api.nod(
|
|
231
|
+
api = API(USERNAME, KEY, header_authentication=False)
|
|
232
|
+
api.nod(**kwargs)
|
|
233
233
|
```
|
|
234
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
235
|
```python
|
|
236
|
-
api = API(USERNAME, KEY
|
|
236
|
+
api = API(USERNAME, KEY)
|
|
237
237
|
api.nod(output_format="csv", **kwargs)
|
|
238
238
|
```
|
|
239
239
|
|
|
@@ -254,7 +254,7 @@ Since we may dealing with large feeds datasets, the python wrapper uses `generat
|
|
|
254
254
|
```python
|
|
255
255
|
from domaintools import API
|
|
256
256
|
|
|
257
|
-
api = API(USERNAME, KEY
|
|
257
|
+
api = API(USERNAME, KEY)
|
|
258
258
|
results = api.nod(sessionID="my-session-id", after=-60)
|
|
259
259
|
|
|
260
260
|
for result in results.response() # generator that holds NOD feeds data for the past 60 seconds and is expected to request only once
|
|
@@ -265,7 +265,7 @@ for result in results.response() # generator that holds NOD feeds data for the p
|
|
|
265
265
|
```python
|
|
266
266
|
from domaintools import API
|
|
267
267
|
|
|
268
|
-
api = API(USERNAME, KEY
|
|
268
|
+
api = API(USERNAME, KEY)
|
|
269
269
|
results = api.nod(sessionID="my-session-id", after=-7200)
|
|
270
270
|
|
|
271
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.5.3
|
|
@@ -6,7 +6,7 @@ from typing import Union
|
|
|
6
6
|
import re
|
|
7
7
|
import ssl
|
|
8
8
|
|
|
9
|
-
from domaintools.constants import Endpoint, ENDPOINT_TO_SOURCE_MAP,
|
|
9
|
+
from domaintools.constants import Endpoint, ENDPOINT_TO_SOURCE_MAP, RTTF_PRODUCTS_LIST, OutputFormat
|
|
10
10
|
from domaintools._version import current as version
|
|
11
11
|
from domaintools.results import (
|
|
12
12
|
GroupedIterable,
|
|
@@ -63,7 +63,8 @@ class API(object):
|
|
|
63
63
|
verify_ssl=True,
|
|
64
64
|
rate_limit=True,
|
|
65
65
|
proxy_url=None,
|
|
66
|
-
always_sign_api_key=
|
|
66
|
+
always_sign_api_key=None,
|
|
67
|
+
header_authentication=None,
|
|
67
68
|
key_sign_hash="sha256",
|
|
68
69
|
app_name="python_wrapper",
|
|
69
70
|
app_version=version,
|
|
@@ -83,6 +84,7 @@ class API(object):
|
|
|
83
84
|
self.proxy_url = proxy_url
|
|
84
85
|
self.extra_request_params = {}
|
|
85
86
|
self.always_sign_api_key = always_sign_api_key
|
|
87
|
+
self.header_authentication = header_authentication
|
|
86
88
|
self.key_sign_hash = key_sign_hash
|
|
87
89
|
self.default_parameters["app_name"] = app_name
|
|
88
90
|
self.default_parameters["app_version"] = app_version
|
|
@@ -124,24 +126,37 @@ class API(object):
|
|
|
124
126
|
def _results(self, product, path, cls=Results, **kwargs):
|
|
125
127
|
"""Returns _results for the specified API path with the specified **kwargs parameters"""
|
|
126
128
|
if product != "account-information" and self.rate_limit and not self.limits_set and not self.limits:
|
|
129
|
+
always_sign_api_key_previous_value = self.always_sign_api_key
|
|
130
|
+
header_authentication_previous_value = self.header_authentication
|
|
127
131
|
self._rate_limit()
|
|
132
|
+
# Reset always_sign_api_key and header_authentication to its original User-set values as these might be affected when self.account_information() was executed
|
|
133
|
+
self.always_sign_api_key = always_sign_api_key_previous_value
|
|
134
|
+
self.header_authentication = header_authentication_previous_value
|
|
128
135
|
|
|
129
136
|
uri = "/".join((self._rest_api_url, path.lstrip("/")))
|
|
130
137
|
parameters = self.default_parameters.copy()
|
|
131
138
|
parameters["api_username"] = self.username
|
|
132
|
-
|
|
133
|
-
self.
|
|
139
|
+
is_rttf_product = product in RTTF_PRODUCTS_LIST
|
|
140
|
+
self._handle_api_key_parameters(is_rttf_product)
|
|
141
|
+
self.handle_api_key(is_rttf_product, path, parameters)
|
|
134
142
|
parameters.update({key: str(value).lower() if value in (True, False) else value for key, value in kwargs.items() if value is not None})
|
|
135
143
|
|
|
136
144
|
return cls(self, product, uri, **parameters)
|
|
137
145
|
|
|
138
|
-
def
|
|
146
|
+
def _handle_api_key_parameters(self, is_rttf_product):
|
|
147
|
+
if self.always_sign_api_key is None:
|
|
148
|
+
self.always_sign_api_key = not is_rttf_product
|
|
149
|
+
|
|
150
|
+
if self.header_authentication is None:
|
|
151
|
+
self.header_authentication = is_rttf_product
|
|
152
|
+
|
|
153
|
+
def handle_api_key(self, is_rttf_product, path, parameters):
|
|
139
154
|
if self.https and not self.always_sign_api_key:
|
|
140
|
-
|
|
141
|
-
parameters["X-Api-Key"] = self.key
|
|
142
|
-
else:
|
|
143
|
-
parameters["api_key"] = self.key
|
|
155
|
+
parameters["api_key"] = self.key
|
|
144
156
|
else:
|
|
157
|
+
if is_rttf_product:
|
|
158
|
+
# As per requirement in IDEV-2272, raise this error when the user explicitly sets signing of API key for RTTF endpoints
|
|
159
|
+
raise ValueError("Real Time Threat Feeds do not support signed API keys.")
|
|
145
160
|
if self.key_sign_hash and self.key_sign_hash in AVAILABLE_KEY_SIGN_HASHES:
|
|
146
161
|
signing_hash = eval(self.key_sign_hash)
|
|
147
162
|
else:
|
|
@@ -9,7 +9,7 @@ from copy import deepcopy
|
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from httpx import Client
|
|
11
11
|
|
|
12
|
-
from domaintools.constants import
|
|
12
|
+
from domaintools.constants import RTTF_PRODUCTS_LIST, OutputFormat, HEADER_ACCEPT_KEY_CSV_FORMAT
|
|
13
13
|
from domaintools.exceptions import (
|
|
14
14
|
BadRequestException,
|
|
15
15
|
InternalServerErrorException,
|
|
@@ -75,26 +75,30 @@ class Results(MutableMapping, MutableSequence):
|
|
|
75
75
|
|
|
76
76
|
return wait_for
|
|
77
77
|
|
|
78
|
-
def
|
|
79
|
-
parameters = deepcopy(self.kwargs)
|
|
80
|
-
parameters.pop("output_format", None)
|
|
81
|
-
parameters.pop(
|
|
82
|
-
"format", None
|
|
83
|
-
) # For some unknownn reasons, even if "format" is not included in the cli params for feeds endpoint, it is being populated thus we need to remove it. Happens only if using CLI.
|
|
78
|
+
def _get_session_params_and_headers(self):
|
|
84
79
|
headers = {}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
parameters = deepcopy(self.kwargs)
|
|
81
|
+
is_rttf_product = self.product in RTTF_PRODUCTS_LIST
|
|
82
|
+
if is_rttf_product:
|
|
83
|
+
parameters.pop("output_format", None)
|
|
84
|
+
parameters.pop(
|
|
85
|
+
"format", None
|
|
86
|
+
) # For some unknownn reasons, even if "format" is not included in the cli params for feeds endpoint, it is being populated thus we need to remove it. Happens only if using CLI.
|
|
87
|
+
if self.kwargs.get("output_format", OutputFormat.JSONL.value) == OutputFormat.CSV.value:
|
|
88
|
+
parameters["headers"] = int(bool(self.kwargs.get("headers", False)))
|
|
89
|
+
headers["accept"] = HEADER_ACCEPT_KEY_CSV_FORMAT
|
|
90
|
+
|
|
91
|
+
if self.api.header_authentication:
|
|
92
|
+
header_key_for_api_key = "X-Api-Key" if is_rttf_product else "X-API-Key"
|
|
93
|
+
headers[header_key_for_api_key] = self.api.key
|
|
92
94
|
|
|
93
95
|
return {"parameters": parameters, "headers": headers}
|
|
94
96
|
|
|
95
97
|
def _make_request(self):
|
|
96
98
|
|
|
97
99
|
with Client(verify=self.api.verify_ssl, proxy=self.api.proxy_url, timeout=None) as session:
|
|
100
|
+
session_params_and_headers = self._get_session_params_and_headers()
|
|
101
|
+
headers = session_params_and_headers.get("headers")
|
|
98
102
|
if self.product in [
|
|
99
103
|
"iris-investigate",
|
|
100
104
|
"iris-enrich",
|
|
@@ -102,18 +106,14 @@ class Results(MutableMapping, MutableSequence):
|
|
|
102
106
|
]:
|
|
103
107
|
post_data = self.kwargs.copy()
|
|
104
108
|
post_data.update(self.api.extra_request_params)
|
|
105
|
-
return session.post(url=self.url, data=post_data)
|
|
109
|
+
return session.post(url=self.url, data=post_data, headers=headers)
|
|
106
110
|
elif self.product in ["iris-detect-manage-watchlist-domains"]:
|
|
107
111
|
patch_data = self.kwargs.copy()
|
|
108
112
|
patch_data.update(self.api.extra_request_params)
|
|
109
|
-
return session.patch(url=self.url, json=patch_data)
|
|
110
|
-
elif self.product in FEEDS_PRODUCTS_LIST:
|
|
111
|
-
session_params = self._get_session_params()
|
|
112
|
-
parameters = session_params.get("parameters")
|
|
113
|
-
headers = session_params.get("headers")
|
|
114
|
-
return session.get(url=self.url, params=parameters, headers=headers, **self.api.extra_request_params)
|
|
113
|
+
return session.patch(url=self.url, json=patch_data, headers=headers)
|
|
115
114
|
else:
|
|
116
|
-
|
|
115
|
+
parameters = session_params_and_headers.get("parameters")
|
|
116
|
+
return session.get(url=self.url, params=parameters, headers=headers, **self.api.extra_request_params)
|
|
117
117
|
|
|
118
118
|
def _get_results(self):
|
|
119
119
|
wait_for = self._wait_time()
|
|
@@ -170,7 +170,7 @@ class Results(MutableMapping, MutableSequence):
|
|
|
170
170
|
|
|
171
171
|
def setStatus(self, code, response=None):
|
|
172
172
|
self._status = code
|
|
173
|
-
if code == 200 or (self.product in
|
|
173
|
+
if code == 200 or (self.product in RTTF_PRODUCTS_LIST and code == 206):
|
|
174
174
|
return
|
|
175
175
|
|
|
176
176
|
reason = None
|
|
@@ -9,7 +9,7 @@ from typing import Optional, Dict, Tuple
|
|
|
9
9
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
10
|
|
|
11
11
|
from domaintools.api import API
|
|
12
|
-
from domaintools.constants import Endpoint,
|
|
12
|
+
from domaintools.constants import Endpoint, RTTF_PRODUCTS_LIST, OutputFormat
|
|
13
13
|
from domaintools.cli.utils import get_file_extension
|
|
14
14
|
from domaintools.exceptions import ServiceException
|
|
15
15
|
from domaintools._version import current as version
|
|
@@ -110,7 +110,7 @@ class DTCLICommand:
|
|
|
110
110
|
def _get_formatted_output(cls, cmd_name: str, response, out_format: str = "json"):
|
|
111
111
|
if cmd_name in ("available_api_calls",):
|
|
112
112
|
return "\n".join(response)
|
|
113
|
-
if response.product in
|
|
113
|
+
if response.product in RTTF_PRODUCTS_LIST:
|
|
114
114
|
return "\n".join([data for data in response.response()])
|
|
115
115
|
return str(getattr(response, out_format) if out_format != "list" else response.as_list())
|
|
116
116
|
|
|
@@ -180,6 +180,7 @@ class DTCLICommand:
|
|
|
180
180
|
out_file = params.pop("out_file", sys.stdout)
|
|
181
181
|
verify_ssl = params.pop("no_verify_ssl", False)
|
|
182
182
|
always_sign_api_key = params.pop("no_sign_api_key", False)
|
|
183
|
+
header_authentication = params.pop("no_header_authentication", False)
|
|
183
184
|
source = None
|
|
184
185
|
|
|
185
186
|
if "src_file" in params:
|
|
@@ -214,6 +215,7 @@ class DTCLICommand:
|
|
|
214
215
|
verify_ssl=verify_ssl,
|
|
215
216
|
rate_limit=rate_limit,
|
|
216
217
|
always_sign_api_key=always_sign_api_key,
|
|
218
|
+
header_authentication=header_authentication,
|
|
217
219
|
)
|
|
218
220
|
dt_api_func = getattr(dt_api, name)
|
|
219
221
|
|
|
@@ -229,7 +231,7 @@ class DTCLICommand:
|
|
|
229
231
|
|
|
230
232
|
if isinstance(out_file, _io.TextIOWrapper):
|
|
231
233
|
# use rich `print` command to prettify the ouput in sys.stdout
|
|
232
|
-
if response.product in
|
|
234
|
+
if response.product in RTTF_PRODUCTS_LIST:
|
|
233
235
|
print(output)
|
|
234
236
|
else:
|
|
235
237
|
print(response)
|
|
@@ -32,8 +32,8 @@ def feeds_nad(
|
|
|
32
32
|
"--no-sign-api-key",
|
|
33
33
|
help="Skip signing of api key",
|
|
34
34
|
),
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
no_header_authentication: bool = typer.Option(
|
|
36
|
+
False,
|
|
37
37
|
"--no-header-auth",
|
|
38
38
|
help="Don't use header authentication",
|
|
39
39
|
),
|
|
@@ -112,8 +112,8 @@ def feeds_nod(
|
|
|
112
112
|
"--no-sign-api-key",
|
|
113
113
|
help="Skip signing of api key",
|
|
114
114
|
),
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
no_header_authentication: bool = typer.Option(
|
|
116
|
+
False,
|
|
117
117
|
"--no-header-auth",
|
|
118
118
|
help="Don't use header authentication",
|
|
119
119
|
),
|
|
@@ -192,8 +192,8 @@ def feeds_domainrdap(
|
|
|
192
192
|
"--no-sign-api-key",
|
|
193
193
|
help="Skip signing of api key",
|
|
194
194
|
),
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
no_header_authentication: bool = typer.Option(
|
|
196
|
+
False,
|
|
197
197
|
"--no-header-auth",
|
|
198
198
|
help="Don't use header authentication",
|
|
199
199
|
),
|
|
@@ -260,8 +260,8 @@ def feeds_domaindiscovery(
|
|
|
260
260
|
"--no-sign-api-key",
|
|
261
261
|
help="Skip signing of api key",
|
|
262
262
|
),
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
no_header_authentication: bool = typer.Option(
|
|
264
|
+
False,
|
|
265
265
|
"--no-header-auth",
|
|
266
266
|
help="Don't use header authentication",
|
|
267
267
|
),
|
|
@@ -340,8 +340,8 @@ def feeds_noh(
|
|
|
340
340
|
"--no-sign-api-key",
|
|
341
341
|
help="Skip signing of api key",
|
|
342
342
|
),
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
no_header_authentication: bool = typer.Option(
|
|
344
|
+
False,
|
|
345
345
|
"--no-header-auth",
|
|
346
346
|
help="Don't use header authentication",
|
|
347
347
|
),
|
|
@@ -420,8 +420,8 @@ def feeds_domainhotlist(
|
|
|
420
420
|
"--no-sign-api-key",
|
|
421
421
|
help="Skip signing of api key",
|
|
422
422
|
),
|
|
423
|
-
|
|
424
|
-
|
|
423
|
+
no_header_authentication: bool = typer.Option(
|
|
424
|
+
False,
|
|
425
425
|
"--no-header-auth",
|
|
426
426
|
help="Don't use header authentication",
|
|
427
427
|
),
|
|
@@ -500,8 +500,8 @@ def feeds_realtime_domain_risk(
|
|
|
500
500
|
"--no-sign-api-key",
|
|
501
501
|
help="Skip signing of api key",
|
|
502
502
|
),
|
|
503
|
-
|
|
504
|
-
|
|
503
|
+
no_header_authentication: bool = typer.Option(
|
|
504
|
+
False,
|
|
505
505
|
"--no-header-auth",
|
|
506
506
|
help="Don't use header authentication",
|
|
507
507
|
),
|
|
@@ -18,19 +18,11 @@ from domaintools.cli import constants as c
|
|
|
18
18
|
def iris_investigate(
|
|
19
19
|
ctx: typer.Context,
|
|
20
20
|
domains: str = typer.Option(None, "-d", "--domains", help="Domains to use."),
|
|
21
|
-
data_updated_after: str = typer.Option(
|
|
22
|
-
|
|
23
|
-
),
|
|
24
|
-
expiration_date: str = typer.Option(
|
|
25
|
-
None, "--expiration-date", help="The expiration date."
|
|
26
|
-
),
|
|
21
|
+
data_updated_after: str = typer.Option(None, "--data-updated-after", help="The data updated after."),
|
|
22
|
+
expiration_date: str = typer.Option(None, "--expiration-date", help="The expiration date."),
|
|
27
23
|
create_date: str = typer.Option(None, "--create-date", help="The create date."),
|
|
28
|
-
active: bool = typer.Option(
|
|
29
|
-
|
|
30
|
-
),
|
|
31
|
-
search_hash: str = typer.Option(
|
|
32
|
-
None, "--search-hash", help="The search hash to use"
|
|
33
|
-
),
|
|
24
|
+
active: bool = typer.Option(None, "--active", help="The domains that are in active state"),
|
|
25
|
+
search_hash: str = typer.Option(None, "--search-hash", help="The search hash to use"),
|
|
34
26
|
src_file: str = typer.Option(
|
|
35
27
|
None,
|
|
36
28
|
"-s",
|
|
@@ -59,14 +51,17 @@ def iris_investigate(
|
|
|
59
51
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
60
52
|
callback=DTCLICommand.validate_format_input,
|
|
61
53
|
),
|
|
62
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
63
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
64
|
-
),
|
|
54
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
65
55
|
no_verify_ssl: bool = typer.Option(
|
|
66
56
|
False,
|
|
67
57
|
"--no-verify-ssl",
|
|
68
58
|
help="Skip verification of SSL certificate when making HTTPs API calls",
|
|
69
59
|
),
|
|
60
|
+
no_header_authentication: bool = typer.Option(
|
|
61
|
+
False,
|
|
62
|
+
"--no-header-auth",
|
|
63
|
+
help="Don't use header authentication",
|
|
64
|
+
),
|
|
70
65
|
):
|
|
71
66
|
|
|
72
67
|
extra_args = ctx.args.copy()
|
|
@@ -74,9 +69,7 @@ def iris_investigate(
|
|
|
74
69
|
if "ssl_hash" in kwargs:
|
|
75
70
|
# silently remove the ':' if present.
|
|
76
71
|
ssl_hash_value = kwargs["ssl_hash"]
|
|
77
|
-
kwargs["ssl_hash"] = remove_special_char_in_string(
|
|
78
|
-
ssl_hash_value, special_char=":"
|
|
79
|
-
)
|
|
72
|
+
kwargs["ssl_hash"] = remove_special_char_in_string(ssl_hash_value, special_char=":")
|
|
80
73
|
|
|
81
74
|
DTCLICommand.run(name=c.IRIS_INVESTIGATE, params=ctx.params, **kwargs)
|
|
82
75
|
|
|
@@ -117,14 +110,17 @@ def iris_enrich(
|
|
|
117
110
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
118
111
|
callback=DTCLICommand.validate_format_input,
|
|
119
112
|
),
|
|
120
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
121
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
122
|
-
),
|
|
113
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
123
114
|
no_verify_ssl: bool = typer.Option(
|
|
124
115
|
False,
|
|
125
116
|
"--no-verify-ssl",
|
|
126
117
|
help="Skip verification of SSL certificate when making HTTPs API calls",
|
|
127
118
|
),
|
|
119
|
+
no_header_authentication: bool = typer.Option(
|
|
120
|
+
False,
|
|
121
|
+
"--no-header-auth",
|
|
122
|
+
help="Don't use header authentication",
|
|
123
|
+
),
|
|
128
124
|
):
|
|
129
125
|
DTCLICommand.run(name=c.IRIS_ENRICH, params=ctx.params)
|
|
130
126
|
|
|
@@ -142,9 +138,7 @@ def iris(
|
|
|
142
138
|
nameserver: str = typer.Option(None, "--nameserver", help="Nameserver to use."),
|
|
143
139
|
registrar: str = typer.Option(None, "--registrar", help="Registrar to use."),
|
|
144
140
|
registrant: str = typer.Option(None, "--registrant", help="Registrant to use."),
|
|
145
|
-
registrant_org: str = typer.Option(
|
|
146
|
-
None, "--registrant-org", help="Registrant Org to use."
|
|
147
|
-
),
|
|
141
|
+
registrant_org: str = typer.Option(None, "--registrant-org", help="Registrant Org to use."),
|
|
148
142
|
user: str = typer.Option(None, "-u", "--user", help="Domaintools API Username."),
|
|
149
143
|
key: str = typer.Option(None, "-k", "--key", help="DomainTools API key"),
|
|
150
144
|
creds_file: str = typer.Option(
|
|
@@ -166,14 +160,17 @@ def iris(
|
|
|
166
160
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
167
161
|
callback=DTCLICommand.validate_format_input,
|
|
168
162
|
),
|
|
169
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
170
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
171
|
-
),
|
|
163
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
172
164
|
no_verify_ssl: bool = typer.Option(
|
|
173
165
|
False,
|
|
174
166
|
"--no-verify-ssl",
|
|
175
167
|
help="Skip verification of SSL certificate when making HTTPs API calls",
|
|
176
168
|
),
|
|
169
|
+
no_header_authentication: bool = typer.Option(
|
|
170
|
+
False,
|
|
171
|
+
"--no-header-auth",
|
|
172
|
+
help="Don't use header authentication",
|
|
173
|
+
),
|
|
177
174
|
):
|
|
178
175
|
params = ctx.params.copy()
|
|
179
176
|
if (
|
|
@@ -183,7 +183,3 @@ def validate_feeds_parameters(params):
|
|
|
183
183
|
endpoint = params.get("endpoint")
|
|
184
184
|
if endpoint == Endpoint.DOWNLOAD.value and format == OutputFormat.CSV.value:
|
|
185
185
|
raise ValueError(f"{format} format is not available in {Endpoint.DOWNLOAD.value} API.")
|
|
186
|
-
|
|
187
|
-
if endpoint == Endpoint.DOWNLOAD.value and params.get("header_authentication", True):
|
|
188
|
-
# For download endpoint, header_authentication will be False by default
|
|
189
|
-
params["header_authentication"] = False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: domaintools_api
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
4
4
|
Summary: DomainTools Official Python API
|
|
5
5
|
Author-email: DomainTools <integrations@domaintools.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -265,25 +265,25 @@ Please see the [supported versions](https://github.com/DomainTools/python_api/ra
|
|
|
265
265
|
for the DomainTools Python support policy.
|
|
266
266
|
|
|
267
267
|
|
|
268
|
-
Real-Time Threat
|
|
268
|
+
Real-Time Threat Feeds
|
|
269
269
|
===================
|
|
270
270
|
|
|
271
|
-
Real-Time Threat
|
|
271
|
+
Real-Time Threat 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.
|
|
272
272
|
|
|
273
273
|
Custom parameters aside from the common `GET` Request parameters:
|
|
274
274
|
- `endpoint` (choose either `download` or `feed` API endpoint - default is `feed`)
|
|
275
275
|
```python
|
|
276
|
-
api = API(USERNAME, KEY
|
|
276
|
+
api = API(USERNAME, KEY)
|
|
277
277
|
api.nod(endpoint="feed", **kwargs)
|
|
278
278
|
```
|
|
279
279
|
- `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.
|
|
280
280
|
```python
|
|
281
|
-
api = API(USERNAME, KEY,
|
|
282
|
-
api.nod(
|
|
281
|
+
api = API(USERNAME, KEY, header_authentication=False)
|
|
282
|
+
api.nod(**kwargs)
|
|
283
283
|
```
|
|
284
284
|
- `output_format`: (choose either `csv` or `jsonl` - default is `jsonl`). Cannot be used in `domainrdap` feeds. Additionally, `csv` is not available for `download` endpoints.
|
|
285
285
|
```python
|
|
286
|
-
api = API(USERNAME, KEY
|
|
286
|
+
api = API(USERNAME, KEY)
|
|
287
287
|
api.nod(output_format="csv", **kwargs)
|
|
288
288
|
```
|
|
289
289
|
|
|
@@ -304,7 +304,7 @@ Since we may dealing with large feeds datasets, the python wrapper uses `generat
|
|
|
304
304
|
```python
|
|
305
305
|
from domaintools import API
|
|
306
306
|
|
|
307
|
-
api = API(USERNAME, KEY
|
|
307
|
+
api = API(USERNAME, KEY)
|
|
308
308
|
results = api.nod(sessionID="my-session-id", after=-60)
|
|
309
309
|
|
|
310
310
|
for result in results.response() # generator that holds NOD feeds data for the past 60 seconds and is expected to request only once
|
|
@@ -315,7 +315,7 @@ for result in results.response() # generator that holds NOD feeds data for the p
|
|
|
315
315
|
```python
|
|
316
316
|
from domaintools import API
|
|
317
317
|
|
|
318
|
-
api = API(USERNAME, KEY
|
|
318
|
+
api = API(USERNAME, KEY)
|
|
319
319
|
results = api.nod(sessionID="my-session-id", after=-7200)
|
|
320
320
|
|
|
321
321
|
for partial_result in results.response() # generator that holds NOD feeds data for the past 2 hours and is expected to request multiple times
|
|
@@ -6,7 +6,7 @@ from copy import deepcopy
|
|
|
6
6
|
from httpx import AsyncClient
|
|
7
7
|
|
|
8
8
|
from domaintools.base_results import Results
|
|
9
|
-
from domaintools.constants import
|
|
9
|
+
from domaintools.constants import RTTF_PRODUCTS_LIST, OutputFormat, HEADER_ACCEPT_KEY_CSV_FORMAT
|
|
10
10
|
from domaintools.exceptions import ServiceUnavailableException
|
|
11
11
|
|
|
12
12
|
|
|
@@ -43,21 +43,19 @@ class AsyncResults(Results):
|
|
|
43
43
|
return self.__awaitable__().__await__()
|
|
44
44
|
|
|
45
45
|
async def _make_async_request(self, session):
|
|
46
|
+
session_params_and_headers = self._get_session_params_and_headers()
|
|
47
|
+
headers = session_params_and_headers.get("headers")
|
|
46
48
|
if self.product in ["iris-investigate", "iris-enrich", "iris-detect-escalate-domains"]:
|
|
47
49
|
post_data = self.kwargs.copy()
|
|
48
50
|
post_data.update(self.api.extra_request_params)
|
|
49
|
-
results = await session.post(url=self.url, data=post_data)
|
|
51
|
+
results = await session.post(url=self.url, data=post_data, headers=headers)
|
|
50
52
|
elif self.product in ["iris-detect-manage-watchlist-domains"]:
|
|
51
53
|
patch_data = self.kwargs.copy()
|
|
52
|
-
patch_data.update(self.api.extra_request_params)
|
|
54
|
+
patch_data.update(self.api.extra_request_params, headers=headers)
|
|
53
55
|
results = await session.patch(url=self.url, json=patch_data)
|
|
54
|
-
elif self.product in FEEDS_PRODUCTS_LIST:
|
|
55
|
-
session_params = self._get_session_params()
|
|
56
|
-
parameters = session_params.get("parameters")
|
|
57
|
-
headers = session_params.get("headers")
|
|
58
|
-
results = await session.get(url=self.url, params=parameters, headers=headers, **self.api.extra_request_params)
|
|
59
56
|
else:
|
|
60
|
-
|
|
57
|
+
parameters = session_params_and_headers.get("parameters")
|
|
58
|
+
results = await session.get(url=self.url, params=parameters, headers=headers, **self.api.extra_request_params)
|
|
61
59
|
if results:
|
|
62
60
|
self.setStatus(results.status_code, results)
|
|
63
61
|
if self.kwargs.get("format", "json") == "json":
|
|
@@ -5,6 +5,7 @@ from os import environ
|
|
|
5
5
|
import json
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
+
from datetime import datetime, timedelta, timezone
|
|
8
9
|
from inspect import isgenerator
|
|
9
10
|
|
|
10
11
|
from domaintools import API, exceptions
|
|
@@ -476,7 +477,7 @@ def test_iris_detect_watched_domains():
|
|
|
476
477
|
assert len(detect_results["watchlist_domains"]) == 3
|
|
477
478
|
|
|
478
479
|
detect_results = api.iris_detect_watched_domains(escalation_types="blocked")
|
|
479
|
-
assert detect_results["count"] ==
|
|
480
|
+
assert detect_results["count"] == 1
|
|
480
481
|
|
|
481
482
|
|
|
482
483
|
@vcr.use_cassette
|
|
@@ -513,7 +514,7 @@ def test_limit_exceeded():
|
|
|
513
514
|
|
|
514
515
|
@vcr.use_cassette
|
|
515
516
|
def test_newly_observed_domains_feed():
|
|
516
|
-
results = feeds_api.nod(after="-60",
|
|
517
|
+
results = feeds_api.nod(after="-60", top=5)
|
|
517
518
|
for response in results.response():
|
|
518
519
|
assert results.status == 200
|
|
519
520
|
|
|
@@ -529,7 +530,7 @@ def test_newly_observed_domains_feed():
|
|
|
529
530
|
|
|
530
531
|
@vcr.use_cassette
|
|
531
532
|
def test_newly_observed_hosts_feed():
|
|
532
|
-
results = feeds_api.noh(after="-60",
|
|
533
|
+
results = feeds_api.noh(after="-60", top=5)
|
|
533
534
|
for response in results.response():
|
|
534
535
|
assert results.status == 200
|
|
535
536
|
|
|
@@ -544,8 +545,12 @@ def test_newly_observed_hosts_feed():
|
|
|
544
545
|
|
|
545
546
|
|
|
546
547
|
@vcr.use_cassette
|
|
548
|
+
@pytest.mark.skip(reason="Failing test due to unknown reason. Still investigating the root-cause.")
|
|
547
549
|
def test_newly_observed_domains_feed_pagination():
|
|
548
|
-
|
|
550
|
+
now_utc = datetime.now(timezone.utc)
|
|
551
|
+
one_hour_ago = now_utc - timedelta(hours=1)
|
|
552
|
+
after = one_hour_ago.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
553
|
+
results = feeds_api.nod(sessionID="integrations-testing", after=after)
|
|
549
554
|
page_count = 0
|
|
550
555
|
for response in results.response():
|
|
551
556
|
rows = response.strip().split("\n")
|
|
@@ -564,7 +569,7 @@ def test_newly_observed_domains_feed_pagination():
|
|
|
564
569
|
|
|
565
570
|
@vcr.use_cassette
|
|
566
571
|
def test_newly_active_domains_feed():
|
|
567
|
-
results = feeds_api.nad(after="-60",
|
|
572
|
+
results = feeds_api.nad(after="-60", top=5)
|
|
568
573
|
for response in results.response():
|
|
569
574
|
assert results.status == 200
|
|
570
575
|
|
|
@@ -580,7 +585,7 @@ def test_newly_active_domains_feed():
|
|
|
580
585
|
|
|
581
586
|
@vcr.use_cassette
|
|
582
587
|
def test_domainrdap_feed():
|
|
583
|
-
results = feeds_api.domainrdap(after="-60", top=2
|
|
588
|
+
results = feeds_api.domainrdap(after="-60", top=2)
|
|
584
589
|
for response in results.response():
|
|
585
590
|
assert results.status == 200
|
|
586
591
|
|
|
@@ -601,7 +606,7 @@ def test_domainrdap_feed():
|
|
|
601
606
|
|
|
602
607
|
@vcr.use_cassette
|
|
603
608
|
def test_domain_discovery_feed():
|
|
604
|
-
results = feeds_api.domaindiscovery(after="-60",
|
|
609
|
+
results = feeds_api.domaindiscovery(after="-60", top=5)
|
|
605
610
|
for response in results.response():
|
|
606
611
|
assert results.status == 200
|
|
607
612
|
|
|
@@ -617,7 +622,7 @@ def test_domain_discovery_feed():
|
|
|
617
622
|
|
|
618
623
|
@vcr.use_cassette
|
|
619
624
|
def test_domainrdap_feed_not_api_header_auth():
|
|
620
|
-
results = feeds_api.domainrdap(after="-60", sessiondID="integrations-testing", top=5
|
|
625
|
+
results = feeds_api.domainrdap(after="-60", sessiondID="integrations-testing", top=5)
|
|
621
626
|
for response in results.response():
|
|
622
627
|
assert results.status == 200
|
|
623
628
|
|
|
@@ -638,14 +643,14 @@ def test_domainrdap_feed_not_api_header_auth():
|
|
|
638
643
|
|
|
639
644
|
@vcr.use_cassette
|
|
640
645
|
def test_verify_response_is_a_generator():
|
|
641
|
-
results = feeds_api.domaindiscovery(after="-60"
|
|
646
|
+
results = feeds_api.domaindiscovery(after="-60")
|
|
642
647
|
|
|
643
648
|
assert isgenerator(results.response())
|
|
644
649
|
|
|
645
650
|
|
|
646
651
|
@vcr.use_cassette
|
|
647
652
|
def test_feeds_endpoint_should_non_header_auth_be_the_default():
|
|
648
|
-
results = feeds_api.domaindiscovery(after="-60", endpoint="download")
|
|
653
|
+
results = feeds_api.domaindiscovery(after="-60", endpoint="download", top=5)
|
|
649
654
|
for response in results.response():
|
|
650
655
|
assert results.status == 200
|
|
651
656
|
|
|
@@ -677,7 +682,7 @@ def test_feeds_endpoint_should_raise_error_if_asked_csv_format_for_download_api(
|
|
|
677
682
|
|
|
678
683
|
@vcr.use_cassette
|
|
679
684
|
def test_realtime_domain_risk():
|
|
680
|
-
results = feeds_api.realtime_domain_risk(after="-60",
|
|
685
|
+
results = feeds_api.realtime_domain_risk(after="-60", top=5)
|
|
681
686
|
for response in results.response():
|
|
682
687
|
assert results.status == 200
|
|
683
688
|
|
|
@@ -697,7 +702,7 @@ def test_realtime_domain_risk():
|
|
|
697
702
|
|
|
698
703
|
@vcr.use_cassette
|
|
699
704
|
def test_domain_hotlist():
|
|
700
|
-
results = feeds_api.domainhotlist(after="-60",
|
|
705
|
+
results = feeds_api.domainhotlist(after="-60", top=5)
|
|
701
706
|
for response in results.response():
|
|
702
707
|
assert results.status == 200
|
|
703
708
|
|
|
@@ -713,3 +718,12 @@ def test_domain_hotlist():
|
|
|
713
718
|
assert "malware_risk" in feed_result.keys()
|
|
714
719
|
assert "proximity_risk" in feed_result.keys()
|
|
715
720
|
assert "overall_risk" in feed_result.keys()
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
@vcr.use_cassette
|
|
724
|
+
def test_feeds_endpoint_should_raise_error_if_signed_api_key_is_used():
|
|
725
|
+
feeds_api.always_sign_api_key = True
|
|
726
|
+
with pytest.raises(ValueError) as excinfo:
|
|
727
|
+
feeds_api.domaindiscovery(after="-60")
|
|
728
|
+
|
|
729
|
+
assert str(excinfo.value) == "Real Time Threat Feeds do not support signed API keys."
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Tests async interaction support for DomainTools APIs"""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from tests.settings import api, vcr
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@vcr.use_cassette
|
|
10
|
+
@pytest.mark.asyncio
|
|
11
|
+
async def test_async_iteration():
|
|
12
|
+
results = await api.domain_search("google")
|
|
13
|
+
assert results
|
|
14
|
+
|
|
15
|
+
list_of_domains = []
|
|
16
|
+
for domain in results:
|
|
17
|
+
list_of_domains += domain
|
|
18
|
+
|
|
19
|
+
for domain in list_of_domains:
|
|
20
|
+
assert type(domain) == str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@vcr.use_cassette
|
|
24
|
+
@pytest.mark.asyncio
|
|
25
|
+
async def test_async_context_manager():
|
|
26
|
+
results = await api.domain_search("google")
|
|
27
|
+
assert results
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@vcr.use_cassette
|
|
31
|
+
@pytest.mark.asyncio
|
|
32
|
+
async def test_async_simple_await():
|
|
33
|
+
results = await api.domain_search("google")
|
|
34
|
+
assert results
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@vcr.use_cassette
|
|
38
|
+
@pytest.mark.asyncio
|
|
39
|
+
async def test_async_simple_await_post():
|
|
40
|
+
investigation_results = await api.iris_investigate(domains=["amazon.com", "google.com"])
|
|
41
|
+
assert investigation_results["results_count"]
|
|
42
|
+
for result in investigation_results:
|
|
43
|
+
assert result["domain"] in ["amazon.com", "google.com"]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@vcr.use_cassette
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_async_simple_await_patch():
|
|
49
|
+
detect_results = await api.iris_detect_manage_watchlist_domains(watchlist_domain_ids=["gae08rdVWG"], state="watched")
|
|
50
|
+
assert detect_results["watchlist_domains"][0]["state"] == "watched"
|
|
@@ -119,17 +119,6 @@ def test_get_pivots():
|
|
|
119
119
|
assert pivots == [["IP ADDRESS", ("199.30.228.112", 4)], ["IP ASN", (17318, 111)], ["IP ISP", ("DomainTools LLC", 222)]]
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
def test_validate_feeds_parameters_update_header_auth_to_False(test_feeds_params):
|
|
123
|
-
test_feeds_params["output_format"] = "jsonl"
|
|
124
|
-
|
|
125
|
-
assert test_feeds_params.get("header_authentication", True) # header_authentication is True whether existing or not
|
|
126
|
-
assert test_feeds_params["endpoint"] == "download"
|
|
127
|
-
|
|
128
|
-
utils.validate_feeds_parameters(test_feeds_params)
|
|
129
|
-
|
|
130
|
-
assert not test_feeds_params["header_authentication"] # header_authentication will be defaulted to False if endpoint is download
|
|
131
|
-
|
|
132
|
-
|
|
133
122
|
def test_validate_feeds_parameters_should_raise_error_if_no_required_params(test_feeds_params):
|
|
134
123
|
test_feeds_params.pop("sessionID", None)
|
|
135
124
|
test_feeds_params.pop("after", None)
|
domaintools_api-2.5.2/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.5.2
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"""Tests async interaction support for DomainTools APIs"""
|
|
2
|
-
import asyncio
|
|
3
|
-
|
|
4
|
-
from tests.settings import api, vcr
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def run_async(future):
|
|
8
|
-
return asyncio.get_event_loop().run_until_complete(future)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@vcr.use_cassette
|
|
12
|
-
def test_async_iteration():
|
|
13
|
-
async def async_iter_test():
|
|
14
|
-
domains = []
|
|
15
|
-
async for domain in api.domain_search('google'):
|
|
16
|
-
domains += domain
|
|
17
|
-
return domains
|
|
18
|
-
|
|
19
|
-
domains = run_async(async_iter_test())
|
|
20
|
-
assert domains
|
|
21
|
-
for domain in domains:
|
|
22
|
-
assert type(domain) == str
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@vcr.use_cassette
|
|
26
|
-
def test_async_context_manager():
|
|
27
|
-
async def async_context_manager_test():
|
|
28
|
-
async with api.domain_search('google') as results:
|
|
29
|
-
return results
|
|
30
|
-
|
|
31
|
-
results = run_async(async_context_manager_test())
|
|
32
|
-
assert results
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@vcr.use_cassette
|
|
36
|
-
def test_async_simple_await():
|
|
37
|
-
async def simple_await():
|
|
38
|
-
results = await api.domain_search('google')
|
|
39
|
-
return results
|
|
40
|
-
|
|
41
|
-
results = run_async(simple_await())
|
|
42
|
-
assert results
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@vcr.use_cassette
|
|
46
|
-
def test_async_simple_await_post():
|
|
47
|
-
async def simple_await():
|
|
48
|
-
results = await api.iris_investigate(domains=['amazon.com', 'google.com'])
|
|
49
|
-
return results
|
|
50
|
-
|
|
51
|
-
investigation_results = run_async(simple_await())
|
|
52
|
-
assert investigation_results['results_count']
|
|
53
|
-
for result in investigation_results:
|
|
54
|
-
assert result['domain'] in ['amazon.com', 'google.com']
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@vcr.use_cassette
|
|
58
|
-
def test_async_simple_await_patch():
|
|
59
|
-
async def simple_await():
|
|
60
|
-
results = await api.iris_detect_manage_watchlist_domains(watchlist_domain_ids=["gae08rdVWG"], state="watched")
|
|
61
|
-
return results
|
|
62
|
-
|
|
63
|
-
detect_results = run_async(simple_await())
|
|
64
|
-
assert detect_results['watchlist_domains'][0]['state'] == "watched"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{domaintools_api-2.5.2 → domaintools_api-2.5.3}/domaintools_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|