Skip to content

Commit 0d21455

Browse files
committed
IDEV-2011: Implement support for API header authentication for Real-Time Threat Intelligence Feeds. Add download endpoint support for nod and nad.
1 parent d85f5ec commit 0d21455

File tree

3 files changed

+37
-20
lines changed

3 files changed

+37
-20
lines changed

domaintools/api.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
filter_by_field,
2020
DTResultFilter,
2121
)
22-
from domaintools.utils import validate_feeds_parameters
22+
from domaintools.utils import get_feeds_products_list, validate_feeds_parameters
2323

2424

2525
AVAILABLE_KEY_SIGN_HASHES = ["sha1", "sha256", "md5"]
@@ -125,14 +125,18 @@ def _results(self, product, path, cls=Results, **kwargs):
125125
uri = "/".join((self._rest_api_url, path.lstrip("/")))
126126
parameters = self.default_parameters.copy()
127127
parameters["api_username"] = self.username
128-
self.handle_api_key(path, parameters)
128+
header_authentication = kwargs.pop("header_authentication", True) # Used only by Real-Time Threat Intelligence Feeds endpoints for now
129+
self.handle_api_key(product, path, parameters, header_authentication)
129130
parameters.update({key: str(value).lower() if value in (True, False) else value for key, value in kwargs.items() if value is not None})
130131

131132
return cls(self, product, uri, **parameters)
132133

133-
def handle_api_key(self, path, parameters):
134+
def handle_api_key(self, product, path, parameters, header_authentication):
134135
if self.https and not self.always_sign_api_key:
135-
parameters["api_key"] = self.key
136+
if product in get_feeds_products_list() and header_authentication:
137+
parameters["X-Api-Key"] = self.key
138+
else:
139+
parameters["api_key"] = self.key
136140
else:
137141
if self.key_sign_hash and self.key_sign_hash in AVAILABLE_KEY_SIGN_HASHES:
138142
signing_hash = eval(self.key_sign_hash)
@@ -1063,28 +1067,32 @@ def iris_detect_ignored_domains(
10631067

10641068
def nod(self, **kwargs):
10651069
"""Returns back list of the newly observed domains feed"""
1066-
sessionID = kwargs.get("sessionID")
1067-
after = kwargs.get("after")
1068-
if not (sessionID or after):
1069-
raise ValueError("sessionID or after (can be both) must be defined")
1070+
validate_feeds_parameters(kwargs)
1071+
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
1072+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint)
1073+
if endpoint == Endpoint.DOWNLOAD.value or kwargs.get("output_format", OutputFormat.JSONL.value) != OutputFormat.CSV.value:
1074+
# headers param is allowed only in Feed API and CSV format
1075+
kwargs.pop("headers", None)
10701076

10711077
return self._results(
1072-
"newly-observed-domains-feed-(api)",
1073-
"v1/feed/nod/",
1078+
f"newly-observed-domains-feed-({source.value})",
1079+
f"v1/{endpoint}/nod/",
10741080
response_path=(),
10751081
**kwargs,
10761082
)
10771083

10781084
def nad(self, **kwargs):
10791085
"""Returns back list of the newly active domains feed"""
1080-
sessionID = kwargs.get("sessionID")
1081-
after = kwargs.get("after")
1082-
if not (sessionID or after):
1083-
raise ValueError("sessionID or after (can be both) must be defined")
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)
10841092

10851093
return self._results(
1086-
"newly-active-domains-feed-(api)",
1087-
"v1/feed/nad/",
1094+
f"newly-active-domains-feed-({source})",
1095+
f"v1/{endpoint}/nad/",
10881096
response_path=(),
10891097
**kwargs,
10901098
)
@@ -1093,10 +1101,10 @@ def domainrdap(self, **kwargs):
10931101
"""Returns changes to global domain registration information, populated by the Registration Data Access Protocol (RDAP)"""
10941102
validate_feeds_parameters(kwargs)
10951103
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
1096-
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint)
1104+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint).value
10971105

10981106
return self._results(
1099-
f"domain-registration-data-access-protocol-feed-({source.value})",
1107+
f"domain-registration-data-access-protocol-feed-({source})",
11001108
f"v1/{endpoint}/domainrdap/",
11011109
response_path=(),
11021110
**kwargs,
@@ -1106,13 +1114,13 @@ def domaindiscovery(self, **kwargs):
11061114
"""Returns new domains as they are either discovered in domain registration information, observed by our global sensor network, or reported by trusted third parties"""
11071115
validate_feeds_parameters(kwargs)
11081116
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
1109-
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint)
1117+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint).value
11101118
if endpoint == Endpoint.DOWNLOAD.value or kwargs.get("output_format", OutputFormat.JSONL.value) != OutputFormat.CSV.value:
11111119
# headers param is allowed only in Feed API and CSV format
11121120
kwargs.pop("headers", None)
11131121

11141122
return self._results(
1115-
f"real-time-domain-discovery-feed-({source.value})",
1123+
f"real-time-domain-discovery-feed-({source})",
11161124
f"v1/{endpoint}/domaindiscovery/",
11171125
response_path=(),
11181126
**kwargs,

domaintools/base_results.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ def _make_request(self):
104104
parameters["headers"] = int(bool(self.kwargs.get("headers", False)))
105105
headers["accept"] = HEADER_ACCEPT_KEY_CSV_FORMAT
106106

107+
header_api_key = parameters.pop("X-Api-Key", None)
108+
if header_api_key:
109+
headers["X-Api-Key"] = header_api_key
110+
107111
return session.get(url=self.url, params=parameters, headers=headers, **self.api.extra_request_params)
108112
else:
109113
return session.get(url=self.url, params=self.kwargs, **self.api.extra_request_params)

domaintools_async/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ async def _make_async_request(self, session):
6262
if self.kwargs.get("output_format", OutputFormat.JSONL.value) == OutputFormat.CSV.value:
6363
parameters["headers"] = int(bool(self.kwargs.get("headers", False)))
6464
headers["accept"] = HEADER_ACCEPT_KEY_CSV_FORMAT
65+
66+
header_api_key = parameters.pop("X-Api-Key", None)
67+
if header_api_key:
68+
headers["X-Api-Key"] = header_api_key
69+
6570
results = await session.get(url=self.url, params=parameters, headers=headers, **self.api.extra_request_params)
6671
else:
6772
results = await session.get(url=self.url, params=self.kwargs, **self.api.extra_request_params)

0 commit comments

Comments
 (0)