domaintools-api 2.1.0__tar.gz → 2.2.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.2.0}/PKG-INFO +2 -7
- domaintools_api-2.2.0/VERSION +1 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/_version.py +1 -1
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/api.py +43 -105
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/base_results.py +9 -34
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/commands/domains.py +60 -63
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/constants.py +1 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/utils.py +1 -0
- domaintools_api-2.2.0/domaintools/results.py +143 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0/domaintools_api.egg-info}/PKG-INFO +2 -7
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools_async/__init__.py +11 -5
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/pyproject.toml +1 -6
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/tests/test_api.py +29 -3
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/tests/test_cli.py +1 -1
- domaintools_api-2.1.0/VERSION +0 -1
- domaintools_api-2.1.0/domaintools/results.py +0 -81
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/LICENSE +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/README.md +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/__init__.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/__init__.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/api.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/commands/__init__.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/commands/accounts.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/commands/detects.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/commands/feeds.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/commands/ips.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/commands/iris.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/commands/phisheye.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/cli/main.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/exceptions.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/filters.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools/utils.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools_api.egg-info/SOURCES.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools_api.egg-info/dependency_links.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools_api.egg-info/entry_points.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools_api.egg-info/requires.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/domaintools_api.egg-info/top_level.txt +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/setup.cfg +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/setup.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/tests/test_async.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/tests/test_filters.py +0 -0
- {domaintools_api-2.1.0 → domaintools_api-2.2.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: domaintools_api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.2.0
|
|
@@ -4,7 +4,13 @@ from hmac import new as hmac
|
|
|
4
4
|
import re
|
|
5
5
|
|
|
6
6
|
from domaintools._version import current as version
|
|
7
|
-
from domaintools.results import
|
|
7
|
+
from domaintools.results import (
|
|
8
|
+
GroupedIterable,
|
|
9
|
+
ParsedWhois,
|
|
10
|
+
ParsedDomainRdap,
|
|
11
|
+
Reputation,
|
|
12
|
+
Results,
|
|
13
|
+
)
|
|
8
14
|
from domaintools.filters import (
|
|
9
15
|
filter_by_riskscore,
|
|
10
16
|
filter_by_expire_date,
|
|
@@ -77,16 +83,12 @@ class API(object):
|
|
|
77
83
|
self._build_api_url(api_url, api_port)
|
|
78
84
|
|
|
79
85
|
if not https:
|
|
80
|
-
raise Exception(
|
|
81
|
-
"The DomainTools API endpoints no longer support http traffic. Please make sure https=True."
|
|
82
|
-
)
|
|
86
|
+
raise Exception("The DomainTools API endpoints no longer support http traffic. Please make sure https=True.")
|
|
83
87
|
if proxy_url:
|
|
84
88
|
if isinstance(proxy_url, str):
|
|
85
89
|
self.proxy_url = {"http://": proxy_url, "https://": proxy_url}
|
|
86
90
|
else:
|
|
87
|
-
raise Exception(
|
|
88
|
-
"Proxy URL must be a string. For example: '127.0.0.1:8888'"
|
|
89
|
-
)
|
|
91
|
+
raise Exception("Proxy URL must be a string. For example: '127.0.0.1:8888'")
|
|
90
92
|
|
|
91
93
|
def _build_api_url(self, api_url=None, api_port=None):
|
|
92
94
|
"""Build the API url based on the given url and port. Defaults to `https://api.domaintools.com`"""
|
|
@@ -110,31 +112,18 @@ class API(object):
|
|
|
110
112
|
hours = limit_hours and 3600 / float(limit_hours)
|
|
111
113
|
minutes = limit_minutes and 60 / float(limit_minutes)
|
|
112
114
|
|
|
113
|
-
self.limits[product["id"]] = {
|
|
114
|
-
"interval": timedelta(seconds=minutes or hours or default)
|
|
115
|
-
}
|
|
115
|
+
self.limits[product["id"]] = {"interval": timedelta(seconds=minutes or hours or default)}
|
|
116
116
|
|
|
117
117
|
def _results(self, product, path, cls=Results, **kwargs):
|
|
118
118
|
"""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
|
-
):
|
|
119
|
+
if product != "account-information" and self.rate_limit and not self.limits_set and not self.limits:
|
|
125
120
|
self._rate_limit()
|
|
126
121
|
|
|
127
122
|
uri = "/".join((self._rest_api_url, path.lstrip("/")))
|
|
128
123
|
parameters = self.default_parameters.copy()
|
|
129
124
|
parameters["api_username"] = self.username
|
|
130
125
|
self.handle_api_key(path, parameters)
|
|
131
|
-
parameters.update(
|
|
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
|
-
)
|
|
126
|
+
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
127
|
|
|
139
128
|
return cls(self, product, uri, **parameters)
|
|
140
129
|
|
|
@@ -147,14 +136,10 @@ class API(object):
|
|
|
147
136
|
else:
|
|
148
137
|
raise ValueError(
|
|
149
138
|
"Invalid value '{0}' for 'key_sign_hash'. "
|
|
150
|
-
"Values available are {1}".format(
|
|
151
|
-
self.key_sign_hash, ",".join(AVAILABLE_KEY_SIGN_HASHES)
|
|
152
|
-
)
|
|
139
|
+
"Values available are {1}".format(self.key_sign_hash, ",".join(AVAILABLE_KEY_SIGN_HASHES))
|
|
153
140
|
)
|
|
154
141
|
|
|
155
|
-
parameters["timestamp"] = datetime.now(timezone.utc).strftime(
|
|
156
|
-
"%Y-%m-%dT%H:%M:%SZ"
|
|
157
|
-
)
|
|
142
|
+
parameters["timestamp"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
158
143
|
parameters["signature"] = hmac(
|
|
159
144
|
self.key.encode("utf8"),
|
|
160
145
|
"".join([self.username, parameters["timestamp"], path]).encode("utf8"),
|
|
@@ -163,9 +148,7 @@ class API(object):
|
|
|
163
148
|
|
|
164
149
|
def account_information(self, **kwargs):
|
|
165
150
|
"""Provides a snapshot of your accounts current API usage"""
|
|
166
|
-
return self._results(
|
|
167
|
-
"account-information", "/v1/account", items_path=("products",), **kwargs
|
|
168
|
-
)
|
|
151
|
+
return self._results("account-information", "/v1/account", items_path=("products",), **kwargs)
|
|
169
152
|
|
|
170
153
|
def available_api_calls(self):
|
|
171
154
|
"""Provides a list of api calls that you can use based on your account information."""
|
|
@@ -180,25 +163,10 @@ class API(object):
|
|
|
180
163
|
string[1:],
|
|
181
164
|
)
|
|
182
165
|
|
|
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
|
-
)
|
|
166
|
+
api_calls = tuple((api_call for api_call in dir(API) if not api_call.startswith("_") and callable(getattr(API, api_call, None))))
|
|
167
|
+
return sorted([snakecase(p["id"]) for p in self.account_information()["products"] if snakecase(p["id"]) in api_calls])
|
|
198
168
|
|
|
199
|
-
def brand_monitor(
|
|
200
|
-
self, query, exclude=None, domain_status=None, days_back=None, **kwargs
|
|
201
|
-
):
|
|
169
|
+
def brand_monitor(self, query, exclude=None, domain_status=None, days_back=None, **kwargs):
|
|
202
170
|
"""Pass in one or more terms as a list or separated by the pipe character ( | )"""
|
|
203
171
|
if exclude is None:
|
|
204
172
|
exclude = []
|
|
@@ -324,9 +292,16 @@ class API(object):
|
|
|
324
292
|
**kwargs,
|
|
325
293
|
)
|
|
326
294
|
|
|
327
|
-
def
|
|
328
|
-
|
|
329
|
-
|
|
295
|
+
def parsed_domain_rdap(self, query, **kwargs):
|
|
296
|
+
"""Pass in a domain name to see the most recent Domain-RDAP registration record"""
|
|
297
|
+
return self._results(
|
|
298
|
+
"parsed-domain-rdap",
|
|
299
|
+
"/v1/{0}/rdap/parsed/".format(query),
|
|
300
|
+
cls=ParsedDomainRdap,
|
|
301
|
+
**kwargs,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
def registrant_monitor(self, query, exclude=None, days_back=0, limit=None, **kwargs):
|
|
330
305
|
"""One or more terms as a Python list or separated by the pipe character ( | )."""
|
|
331
306
|
if exclude is None:
|
|
332
307
|
exclude = []
|
|
@@ -354,15 +329,11 @@ class API(object):
|
|
|
354
329
|
|
|
355
330
|
def reverse_ip(self, domain=None, limit=None, **kwargs):
|
|
356
331
|
"""Pass in a domain name."""
|
|
357
|
-
return self._results(
|
|
358
|
-
"reverse-ip", "/v1/{0}/reverse-ip".format(domain), limit=limit, **kwargs
|
|
359
|
-
)
|
|
332
|
+
return self._results("reverse-ip", "/v1/{0}/reverse-ip".format(domain), limit=limit, **kwargs)
|
|
360
333
|
|
|
361
334
|
def host_domains(self, ip=None, limit=None, **kwargs):
|
|
362
335
|
"""Pass in an IP address."""
|
|
363
|
-
return self._results(
|
|
364
|
-
"reverse-ip", "/v1/{0}/host-domains".format(ip), limit=limit, **kwargs
|
|
365
|
-
)
|
|
336
|
+
return self._results("reverse-ip", "/v1/{0}/host-domains".format(ip), limit=limit, **kwargs)
|
|
366
337
|
|
|
367
338
|
def reverse_ip_whois(
|
|
368
339
|
self,
|
|
@@ -401,9 +372,7 @@ class API(object):
|
|
|
401
372
|
**kwargs,
|
|
402
373
|
)
|
|
403
374
|
|
|
404
|
-
def reverse_whois(
|
|
405
|
-
self, query, exclude=None, scope="current", mode="purchase", **kwargs
|
|
406
|
-
):
|
|
375
|
+
def reverse_whois(self, query, exclude=None, scope="current", mode="purchase", **kwargs):
|
|
407
376
|
"""List of one or more terms to search for in the Whois record,
|
|
408
377
|
as a Python list or separated with the pipe character ( | ).
|
|
409
378
|
"""
|
|
@@ -423,9 +392,7 @@ class API(object):
|
|
|
423
392
|
"""Pass in a domain name or an IP address to perform a whois lookup."""
|
|
424
393
|
return self._results("whois", "/v1/{0}/whois".format(query), **kwargs)
|
|
425
394
|
|
|
426
|
-
def whois_history(
|
|
427
|
-
self, query, mode=None, sort=None, offset=None, limit=None, **kwargs
|
|
428
|
-
):
|
|
395
|
+
def whois_history(self, query, mode=None, sort=None, offset=None, limit=None, **kwargs):
|
|
429
396
|
"""Pass in a domain name."""
|
|
430
397
|
return self._results(
|
|
431
398
|
"whois-history",
|
|
@@ -484,16 +451,7 @@ class API(object):
|
|
|
484
451
|
"""Performs a search for the provided search terms ANDed together,
|
|
485
452
|
returning the pivot engine row data for the resulting domains.
|
|
486
453
|
"""
|
|
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
|
-
):
|
|
454
|
+
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
455
|
raise ValueError("At least one search term must be specified")
|
|
498
456
|
|
|
499
457
|
return self._results(
|
|
@@ -568,12 +526,8 @@ class API(object):
|
|
|
568
526
|
younger_than_date = kwargs.pop("younger_than_date", {}) or None
|
|
569
527
|
older_than_date = kwargs.pop("older_than_date", {}) or None
|
|
570
528
|
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
|
-
)
|
|
529
|
+
include_domains_with_missing_field = kwargs.pop("include_domains_with_missing_field", {}) or None
|
|
530
|
+
exclude_domains_with_missing_field = kwargs.pop("exclude_domains_with_missing_field", {}) or None
|
|
577
531
|
|
|
578
532
|
filtered_results = DTResultFilter(result_set=results).by(
|
|
579
533
|
[
|
|
@@ -581,12 +535,8 @@ class API(object):
|
|
|
581
535
|
filter_by_expire_date(date=younger_than_date, lookup_type="before"),
|
|
582
536
|
filter_by_expire_date(date=older_than_date, lookup_type="after"),
|
|
583
537
|
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
|
-
),
|
|
538
|
+
filter_by_field(field=include_domains_with_missing_field, filter_type="include"),
|
|
539
|
+
filter_by_field(field=exclude_domains_with_missing_field, filter_type="exclude"),
|
|
590
540
|
]
|
|
591
541
|
)
|
|
592
542
|
|
|
@@ -691,9 +641,7 @@ class API(object):
|
|
|
691
641
|
kwargs["search_hash"] = search_hash
|
|
692
642
|
|
|
693
643
|
if not (kwargs or domains):
|
|
694
|
-
raise ValueError(
|
|
695
|
-
"Need to define investigation using kwarg filters or domains"
|
|
696
|
-
)
|
|
644
|
+
raise ValueError("Need to define investigation using kwarg filters or domains")
|
|
697
645
|
|
|
698
646
|
if isinstance(domains, (list, tuple)):
|
|
699
647
|
domains = ",".join(domains)
|
|
@@ -723,12 +671,8 @@ class API(object):
|
|
|
723
671
|
filter_by_expire_date(date=younger_than_date, lookup_type="before"),
|
|
724
672
|
filter_by_expire_date(date=older_than_date, lookup_type="after"),
|
|
725
673
|
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
|
-
),
|
|
674
|
+
filter_by_field(field=include_domains_with_missing_field, filter_type="include"),
|
|
675
|
+
filter_by_field(field=exclude_domains_with_missing_field, filter_type="exclude"),
|
|
732
676
|
]
|
|
733
677
|
)
|
|
734
678
|
|
|
@@ -768,9 +712,7 @@ class API(object):
|
|
|
768
712
|
|
|
769
713
|
if include_counts:
|
|
770
714
|
if not datetime_counts_since:
|
|
771
|
-
raise ValueError(
|
|
772
|
-
"Need to define datetime_counts_since when include_counts is True"
|
|
773
|
-
)
|
|
715
|
+
raise ValueError("Need to define datetime_counts_since when include_counts is True")
|
|
774
716
|
if isinstance(datetime_counts_since, datetime):
|
|
775
717
|
datetime_counts_since = str(datetime_counts_since.astimezone())
|
|
776
718
|
elif isinstance(datetime_counts_since, str):
|
|
@@ -978,9 +920,7 @@ class API(object):
|
|
|
978
920
|
**kwargs,
|
|
979
921
|
)
|
|
980
922
|
|
|
981
|
-
def iris_detect_manage_watchlist_domains(
|
|
982
|
-
self, watchlist_domain_ids, state, **kwargs
|
|
983
|
-
):
|
|
923
|
+
def iris_detect_manage_watchlist_domains(self, watchlist_domain_ids, state, **kwargs):
|
|
984
924
|
"""Changes the watch state of a list of domains by their Iris Detect domain ID.
|
|
985
925
|
|
|
986
926
|
watchlist_domain_ids: List[str]: required. List of Iris Detect domain IDs to manage.
|
|
@@ -999,9 +939,7 @@ class API(object):
|
|
|
999
939
|
**kwargs,
|
|
1000
940
|
)
|
|
1001
941
|
|
|
1002
|
-
def iris_detect_escalate_domains(
|
|
1003
|
-
self, watchlist_domain_ids, escalation_type, **kwargs
|
|
1004
|
-
):
|
|
942
|
+
def iris_detect_escalate_domains(self, watchlist_domain_ids, escalation_type, **kwargs):
|
|
1005
943
|
"""Changes the escalation type of a list of domains by their Iris Detect domain ID.
|
|
1006
944
|
|
|
1007
945
|
watchlist_domain_ids: List[str]: required. List of Iris Detect domain IDs to escalate.
|
|
@@ -68,9 +68,7 @@ class Results(MutableMapping, MutableSequence):
|
|
|
68
68
|
wait_for = 0
|
|
69
69
|
if now < safe_after:
|
|
70
70
|
wait_for = safe_after - now
|
|
71
|
-
wait_for = float(wait_for.seconds) + (
|
|
72
|
-
float(wait_for.microseconds) / 1000000.0
|
|
73
|
-
)
|
|
71
|
+
wait_for = float(wait_for.seconds) + (float(wait_for.microseconds) / 1000000.0)
|
|
74
72
|
limit["last_scheduled"] = safe_after
|
|
75
73
|
else:
|
|
76
74
|
limit["last_scheduled"] = now
|
|
@@ -79,9 +77,7 @@ class Results(MutableMapping, MutableSequence):
|
|
|
79
77
|
|
|
80
78
|
def _make_request(self):
|
|
81
79
|
|
|
82
|
-
with Client(
|
|
83
|
-
verify=self.api.verify_ssl, proxies=self.api.proxy_url, timeout=None
|
|
84
|
-
) as session:
|
|
80
|
+
with Client(verify=self.api.verify_ssl, proxy=self.api.proxy_url, timeout=None) as session:
|
|
85
81
|
if self.product in [
|
|
86
82
|
"iris-investigate",
|
|
87
83
|
"iris-enrich",
|
|
@@ -95,15 +91,11 @@ class Results(MutableMapping, MutableSequence):
|
|
|
95
91
|
patch_data.update(self.api.extra_request_params)
|
|
96
92
|
return session.patch(url=self.url, json=patch_data)
|
|
97
93
|
else:
|
|
98
|
-
return session.get(
|
|
99
|
-
url=self.url, params=self.kwargs, **self.api.extra_request_params
|
|
100
|
-
)
|
|
94
|
+
return session.get(url=self.url, params=self.kwargs, **self.api.extra_request_params)
|
|
101
95
|
|
|
102
96
|
def _get_results(self):
|
|
103
97
|
wait_for = self._wait_time()
|
|
104
|
-
if self.api.rate_limit and (
|
|
105
|
-
wait_for is None or self.product == "account-information"
|
|
106
|
-
):
|
|
98
|
+
if self.api.rate_limit and (wait_for is None or self.product == "account-information"):
|
|
107
99
|
data = self._make_request()
|
|
108
100
|
if data.status_code == 503: # pragma: no cover
|
|
109
101
|
sleeptime = 60
|
|
@@ -118,9 +110,7 @@ class Results(MutableMapping, MutableSequence):
|
|
|
118
110
|
return data
|
|
119
111
|
|
|
120
112
|
if wait_for > 0:
|
|
121
|
-
log.info(
|
|
122
|
-
"Sleeping for [%s] prior to requesting [%s].", wait_for, self.product
|
|
123
|
-
)
|
|
113
|
+
log.info("Sleeping for [%s] prior to requesting [%s].", wait_for, self.product)
|
|
124
114
|
time.sleep(wait_for)
|
|
125
115
|
return self._make_request()
|
|
126
116
|
|
|
@@ -143,19 +133,13 @@ class Results(MutableMapping, MutableSequence):
|
|
|
143
133
|
self._limit_exceeded_message = message
|
|
144
134
|
|
|
145
135
|
if self._limit_exceeded is True:
|
|
146
|
-
raise ServiceException(
|
|
147
|
-
503, "Limit Exceeded{}".format(self._limit_exceeded_message)
|
|
148
|
-
)
|
|
136
|
+
raise ServiceException(503, "Limit Exceeded{}".format(self._limit_exceeded_message))
|
|
149
137
|
else:
|
|
150
138
|
return self._data
|
|
151
139
|
|
|
152
140
|
def check_limit_exceeded(self):
|
|
153
141
|
if self.kwargs.get("format", "json") == "json":
|
|
154
|
-
if
|
|
155
|
-
"response" in self._data
|
|
156
|
-
and "limit_exceeded" in self._data["response"]
|
|
157
|
-
and self._data["response"]["limit_exceeded"] is True
|
|
158
|
-
):
|
|
142
|
+
if "response" in self._data and "limit_exceeded" in self._data["response"] and self._data["response"]["limit_exceeded"] is True:
|
|
159
143
|
return True, self._data["response"]["message"]
|
|
160
144
|
# TODO: handle html, xml response errors better.
|
|
161
145
|
elif "response" in self._data and "limit_exceeded" in self._data:
|
|
@@ -302,16 +286,7 @@ class Results(MutableMapping, MutableSequence):
|
|
|
302
286
|
)
|
|
303
287
|
|
|
304
288
|
def as_list(self):
|
|
305
|
-
return "\n".join(
|
|
306
|
-
[
|
|
307
|
-
json.dumps(item, indent=4, separators=(",", ": "))
|
|
308
|
-
for item in self._items()
|
|
309
|
-
]
|
|
310
|
-
)
|
|
289
|
+
return "\n".join([json.dumps(item, indent=4, separators=(",", ": ")) for item in self._items()])
|
|
311
290
|
|
|
312
291
|
def __str__(self):
|
|
313
|
-
return str(
|
|
314
|
-
json.dumps(self.data(), indent=4, separators=(",", ": "))
|
|
315
|
-
if self.kwargs.get("format", "json") == "json"
|
|
316
|
-
else self.data()
|
|
317
|
-
)
|
|
292
|
+
return str(json.dumps(self.data(), indent=4, separators=(",", ": ")) if self.kwargs.get("format", "json") == "json" else self.data())
|
|
@@ -16,9 +16,7 @@ def brand_monitor(
|
|
|
16
16
|
ctx: typer.Context,
|
|
17
17
|
query: str = typer.Option(..., "-q", "--query", help="The query to use."),
|
|
18
18
|
exclude: str = typer.Option(None, "--exclude", help="The exclude condition."),
|
|
19
|
-
domain_status: str = typer.Option(
|
|
20
|
-
None, "--domain-status", help="The domain status."
|
|
21
|
-
),
|
|
19
|
+
domain_status: str = typer.Option(None, "--domain-status", help="The domain status."),
|
|
22
20
|
days_back: int = typer.Option(None, "--days-back", help="The days back to check."),
|
|
23
21
|
user: str = typer.Option(None, "-u", "--user", help="Domaintools API Username."),
|
|
24
22
|
key: str = typer.Option(None, "-k", "--key", help="DomainTools API key"),
|
|
@@ -41,9 +39,7 @@ def brand_monitor(
|
|
|
41
39
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
42
40
|
callback=DTCLICommand.validate_format_input,
|
|
43
41
|
),
|
|
44
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
45
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
46
|
-
),
|
|
42
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
47
43
|
no_verify_ssl: bool = typer.Option(
|
|
48
44
|
False,
|
|
49
45
|
"--no-verify-ssl",
|
|
@@ -81,9 +77,7 @@ def domain_profile(
|
|
|
81
77
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
82
78
|
callback=DTCLICommand.validate_format_input,
|
|
83
79
|
),
|
|
84
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
85
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
86
|
-
),
|
|
80
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
87
81
|
no_verify_ssl: bool = typer.Option(
|
|
88
82
|
False,
|
|
89
83
|
"--no-verify-ssl",
|
|
@@ -100,19 +94,13 @@ def domain_profile(
|
|
|
100
94
|
def domain_search(
|
|
101
95
|
ctx: typer.Context,
|
|
102
96
|
query: str = typer.Option(..., "-q", "--query", help="The domain name to query."),
|
|
103
|
-
exclude_query: str = typer.Option(
|
|
104
|
-
None, "--exclude-query", help="The exclusion filter to query."
|
|
105
|
-
),
|
|
97
|
+
exclude_query: str = typer.Option(None, "--exclude-query", help="The exclusion filter to query."),
|
|
106
98
|
max_length: int = typer.Option(25, "--max-length", help="The max length"),
|
|
107
99
|
min_length: int = typer.Option(2, "--min-length", help="The min length"),
|
|
108
100
|
has_hyphen: bool = typer.Option(True, "--has-hyphen", help=""),
|
|
109
101
|
has_number: bool = typer.Option(True, "--has-number", help=""),
|
|
110
|
-
active_only: bool = typer.Option(
|
|
111
|
-
|
|
112
|
-
),
|
|
113
|
-
deleted_only: bool = typer.Option(
|
|
114
|
-
False, "--deleted-only", help="Search for deleted only domains."
|
|
115
|
-
),
|
|
102
|
+
active_only: bool = typer.Option(False, "--active-only", help="Search for active only domains."),
|
|
103
|
+
deleted_only: bool = typer.Option(False, "--deleted-only", help="Search for deleted only domains."),
|
|
116
104
|
anchor_left: bool = typer.Option(False, "--achor-left", help=""),
|
|
117
105
|
anchor_right: bool = typer.Option(False, "--achor-right", help=""),
|
|
118
106
|
page: int = typer.Option(1, "--page", help="Number of pages to return."),
|
|
@@ -137,9 +125,7 @@ def domain_search(
|
|
|
137
125
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
138
126
|
callback=DTCLICommand.validate_format_input,
|
|
139
127
|
),
|
|
140
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
141
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
142
|
-
),
|
|
128
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
143
129
|
no_verify_ssl: bool = typer.Option(
|
|
144
130
|
False,
|
|
145
131
|
"--no-verify-ssl",
|
|
@@ -177,9 +163,7 @@ def hosting_history(
|
|
|
177
163
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
178
164
|
callback=DTCLICommand.validate_format_input,
|
|
179
165
|
),
|
|
180
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
181
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
182
|
-
),
|
|
166
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
183
167
|
no_verify_ssl: bool = typer.Option(
|
|
184
168
|
True,
|
|
185
169
|
"--no-verify-ssl",
|
|
@@ -219,9 +203,7 @@ def name_server_monitor(
|
|
|
219
203
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
220
204
|
callback=DTCLICommand.validate_format_input,
|
|
221
205
|
),
|
|
222
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
223
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
224
|
-
),
|
|
206
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
225
207
|
no_verify_ssl: bool = typer.Option(
|
|
226
208
|
False,
|
|
227
209
|
"--no-verify-ssl",
|
|
@@ -259,9 +241,7 @@ def parsed_whois(
|
|
|
259
241
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
260
242
|
callback=DTCLICommand.validate_format_input,
|
|
261
243
|
),
|
|
262
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
263
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
264
|
-
),
|
|
244
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
265
245
|
no_verify_ssl: bool = typer.Option(
|
|
266
246
|
False,
|
|
267
247
|
"--no-verify-ssl",
|
|
@@ -271,6 +251,44 @@ def parsed_whois(
|
|
|
271
251
|
DTCLICommand.run(name=c.PARSED_WHOIS, params=ctx.params)
|
|
272
252
|
|
|
273
253
|
|
|
254
|
+
@dt_cli.command(
|
|
255
|
+
name=c.PARSED_DOMAIN_RDAP,
|
|
256
|
+
help=get_cli_helptext_by_name(command_name=c.PARSED_DOMAIN_RDAP),
|
|
257
|
+
)
|
|
258
|
+
def parsed_domain_rdap(
|
|
259
|
+
ctx: typer.Context,
|
|
260
|
+
query: str = typer.Option(..., "-q", "--query", help="The domain to query."),
|
|
261
|
+
user: str = typer.Option(None, "-u", "--user", help="Domaintools API Username."),
|
|
262
|
+
key: str = typer.Option(None, "-k", "--key", help="DomainTools API key"),
|
|
263
|
+
creds_file: str = typer.Option(
|
|
264
|
+
"~/.dtapi",
|
|
265
|
+
"-c",
|
|
266
|
+
"--credfile",
|
|
267
|
+
help="Optional file with API username and API key, one per line.",
|
|
268
|
+
),
|
|
269
|
+
rate_limit: bool = typer.Option(
|
|
270
|
+
False,
|
|
271
|
+
"-l",
|
|
272
|
+
"--rate-limit",
|
|
273
|
+
help="Rate limit API calls against the API based on per minute limits.",
|
|
274
|
+
),
|
|
275
|
+
format: str = typer.Option(
|
|
276
|
+
"json",
|
|
277
|
+
"-f",
|
|
278
|
+
"--format",
|
|
279
|
+
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
280
|
+
callback=DTCLICommand.validate_format_input,
|
|
281
|
+
),
|
|
282
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
283
|
+
no_verify_ssl: bool = typer.Option(
|
|
284
|
+
False,
|
|
285
|
+
"--no-verify-ssl",
|
|
286
|
+
help="Skip verification of SSL certificate when making HTTPs API calls",
|
|
287
|
+
),
|
|
288
|
+
):
|
|
289
|
+
DTCLICommand.run(name=c.PARSED_DOMAIN_RDAP, params=ctx.params)
|
|
290
|
+
|
|
291
|
+
|
|
274
292
|
@dt_cli.command(
|
|
275
293
|
name=c.REGISTRANT_MONITOR,
|
|
276
294
|
help=get_cli_helptext_by_name(command_name=c.REGISTRANT_MONITOR),
|
|
@@ -306,9 +324,7 @@ def registrant_monitor(
|
|
|
306
324
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
307
325
|
callback=DTCLICommand.validate_format_input,
|
|
308
326
|
),
|
|
309
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
310
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
311
|
-
),
|
|
327
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
312
328
|
no_verify_ssl: bool = typer.Option(
|
|
313
329
|
False,
|
|
314
330
|
"--no-verify-ssl",
|
|
@@ -347,9 +363,7 @@ def reputation(
|
|
|
347
363
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
348
364
|
callback=DTCLICommand.validate_format_input,
|
|
349
365
|
),
|
|
350
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
351
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
352
|
-
),
|
|
366
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
353
367
|
no_verify_ssl: bool = typer.Option(
|
|
354
368
|
False,
|
|
355
369
|
"--no-verify-ssl",
|
|
@@ -388,9 +402,7 @@ def reverse_ip(
|
|
|
388
402
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
389
403
|
callback=DTCLICommand.validate_format_input,
|
|
390
404
|
),
|
|
391
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
392
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
393
|
-
),
|
|
405
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
394
406
|
no_verify_ssl: bool = typer.Option(
|
|
395
407
|
False,
|
|
396
408
|
"--no-verify-ssl",
|
|
@@ -429,9 +441,7 @@ def reverse_nameserver(
|
|
|
429
441
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
430
442
|
callback=DTCLICommand.validate_format_input,
|
|
431
443
|
),
|
|
432
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
433
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
434
|
-
),
|
|
444
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
435
445
|
no_verify_ssl: bool = typer.Option(
|
|
436
446
|
False,
|
|
437
447
|
"--no-verify-ssl",
|
|
@@ -454,9 +464,7 @@ def reverse_whois(
|
|
|
454
464
|
"--scope",
|
|
455
465
|
help="Sets the scope of the report to include only current Whois records, or to include both current and historic records.",
|
|
456
466
|
),
|
|
457
|
-
mode: str = typer.Option(
|
|
458
|
-
"purchase", "--mode", help="Values must be purchase (the default) or quote"
|
|
459
|
-
),
|
|
467
|
+
mode: str = typer.Option("purchase", "--mode", help="Values must be purchase (the default) or quote"),
|
|
460
468
|
user: str = typer.Option(None, "-u", "--user", help="Domaintools API Username."),
|
|
461
469
|
key: str = typer.Option(None, "-k", "--key", help="DomainTools API key"),
|
|
462
470
|
creds_file: str = typer.Option(
|
|
@@ -478,9 +486,7 @@ def reverse_whois(
|
|
|
478
486
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
479
487
|
callback=DTCLICommand.validate_format_input,
|
|
480
488
|
),
|
|
481
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
482
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
483
|
-
),
|
|
489
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
484
490
|
no_verify_ssl: bool = typer.Option(
|
|
485
491
|
False,
|
|
486
492
|
"--no-verify-ssl",
|
|
@@ -518,9 +524,7 @@ def whois(
|
|
|
518
524
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
519
525
|
callback=DTCLICommand.validate_format_input,
|
|
520
526
|
),
|
|
521
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
522
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
523
|
-
),
|
|
527
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
524
528
|
no_verify_ssl: bool = typer.Option(
|
|
525
529
|
False,
|
|
526
530
|
"--no-verify-ssl",
|
|
@@ -552,9 +556,7 @@ def whois_history(
|
|
|
552
556
|
"--limit",
|
|
553
557
|
help="Specify the maximum number of records to retrieve in an API query.",
|
|
554
558
|
),
|
|
555
|
-
offset: int = typer.Option(
|
|
556
|
-
None, "--offset", help="For paginating requests beyond the limit."
|
|
557
|
-
),
|
|
559
|
+
offset: int = typer.Option(None, "--offset", help="For paginating requests beyond the limit."),
|
|
558
560
|
user: str = typer.Option(None, "-u", "--user", help="Domaintools API Username."),
|
|
559
561
|
key: str = typer.Option(None, "-k", "--key", help="DomainTools API key"),
|
|
560
562
|
creds_file: str = typer.Option(
|
|
@@ -576,9 +578,7 @@ def whois_history(
|
|
|
576
578
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
577
579
|
callback=DTCLICommand.validate_format_input,
|
|
578
580
|
),
|
|
579
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
580
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
581
|
-
),
|
|
581
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
582
582
|
no_verify_ssl: bool = typer.Option(
|
|
583
583
|
False,
|
|
584
584
|
"--no-verify-ssl",
|
|
@@ -616,9 +616,7 @@ def risk(
|
|
|
616
616
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
617
617
|
callback=DTCLICommand.validate_format_input,
|
|
618
618
|
),
|
|
619
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
620
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
621
|
-
),
|
|
619
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
622
620
|
no_verify_ssl: bool = typer.Option(
|
|
623
621
|
False,
|
|
624
622
|
"--no-verify-ssl",
|
|
@@ -656,9 +654,7 @@ def risk_evidence(
|
|
|
656
654
|
help="Output format in {'list', 'json', 'xml', 'html'}",
|
|
657
655
|
callback=DTCLICommand.validate_format_input,
|
|
658
656
|
),
|
|
659
|
-
out_file: typer.FileTextWrite = typer.Option(
|
|
660
|
-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
|
|
661
|
-
),
|
|
657
|
+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
|
|
662
658
|
no_verify_ssl: bool = typer.Option(
|
|
663
659
|
False,
|
|
664
660
|
"--no-verify-ssl",
|
|
@@ -674,6 +670,7 @@ __all__ = [
|
|
|
674
670
|
"domain_search",
|
|
675
671
|
"name_server_monitor",
|
|
676
672
|
"parsed_whois",
|
|
673
|
+
"parsed_domain_rdap",
|
|
677
674
|
"registrant_monitor",
|
|
678
675
|
"reputation",
|
|
679
676
|
"reverse_ip",
|
|
@@ -8,6 +8,7 @@ DOMAIN_PROFILE = "domain_profile"
|
|
|
8
8
|
DOMAIN_SEARCH = "domain_search"
|
|
9
9
|
HOSTING_HISTORY = "hosting_history"
|
|
10
10
|
NAME_SERVER_MONITOR = "name_server_monitor"
|
|
11
|
+
PARSED_DOMAIN_RDAP = "parsed_domain_rdap"
|
|
11
12
|
PARSED_WHOIS = "parsed_whois"
|
|
12
13
|
REGISTRANT_MONITOR = "registrant_monitor"
|
|
13
14
|
REPUTATION = "reputation"
|
|
@@ -60,6 +60,7 @@ _command_help_mapper = {
|
|
|
60
60
|
c.IP_MONITOR: "Pass in the IP Address you wish to query ( i.e. 199.30.228.112 ).",
|
|
61
61
|
c.IP_REGISTRANT_MONITOR: "Query based on free text query terms",
|
|
62
62
|
c.NAME_SERVER_MONITOR: "Pass in the hostname of the Name Server you wish to query ( i.e. dynect.net ).",
|
|
63
|
+
c.PARSED_DOMAIN_RDAP: "Pass in a domain name to see the most recent Domain-RDAP registration record.",
|
|
63
64
|
c.PARSED_WHOIS: "Pass in a domain name.",
|
|
64
65
|
c.REGISTRANT_MONITOR: "One or more terms as a Python list or separated by the pipe character ( | ).",
|
|
65
66
|
c.REPUTATION: "Pass in a domain name to see its reputation score.",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Defines the used Result object based on the current versions and/or features available to Python runtime
|
|
2
|
+
|
|
3
|
+
Additionally, defines any custom result objects that may be used to enable more Pythonic interaction with endpoints.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from itertools import chain
|
|
7
|
+
|
|
8
|
+
try: # pragma: no cover
|
|
9
|
+
from collections import OrderedDict
|
|
10
|
+
except ImportError: # pragma: no cover
|
|
11
|
+
from ordereddict import OrderedDict
|
|
12
|
+
|
|
13
|
+
from itertools import zip_longest
|
|
14
|
+
from domaintools_async import AsyncResults as Results
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Reputation(Results):
|
|
18
|
+
"""Returns the reputation results in a format that can quickly be converted into floats / ints"""
|
|
19
|
+
|
|
20
|
+
def __float__(self):
|
|
21
|
+
return float(self["risk_score"])
|
|
22
|
+
|
|
23
|
+
def __int__(self):
|
|
24
|
+
return int(self["risk_score"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GroupedIterable(Results):
|
|
28
|
+
"""Returns a results item in a format that allows for grouped iteration of mulpitle result lists"""
|
|
29
|
+
|
|
30
|
+
def _items(self):
|
|
31
|
+
if self._items_list is None:
|
|
32
|
+
self._items_list = chain(
|
|
33
|
+
*[zip_longest([], value, fillvalue=key) for key, value in self.response().items() if type(value) in (list, tuple)]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return self._items_list
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ParsedWhois(Results):
|
|
40
|
+
"""Returns the parsed whois results in a format that can quickly be flattened"""
|
|
41
|
+
|
|
42
|
+
def flattened(self):
|
|
43
|
+
"""Returns a flattened version of the parsed whois data"""
|
|
44
|
+
parsed = self["parsed_whois"]
|
|
45
|
+
flat = OrderedDict()
|
|
46
|
+
for key in ("domain", "created_date", "updated_date", "expired_date", "statuses", "name_servers"):
|
|
47
|
+
if key in parsed:
|
|
48
|
+
value = parsed[key]
|
|
49
|
+
flat[key] = " | ".join(value) if type(value) in (list, tuple) else value
|
|
50
|
+
|
|
51
|
+
registrar = parsed.get("registrar", {})
|
|
52
|
+
for key in ("name", "abuse_contact_phone", "abuse_contact_email", "iana_id", "url", "whois_server"):
|
|
53
|
+
if key in registrar:
|
|
54
|
+
flat["registrar_{0}".format(key)] = registrar[key]
|
|
55
|
+
|
|
56
|
+
if "networks" in parsed:
|
|
57
|
+
networks = parsed.get("networks")
|
|
58
|
+
for network in networks:
|
|
59
|
+
id = network.get("id")
|
|
60
|
+
for key in ("range", "asn", "org", "parent", "customer", "country", "phone", "status", "source", "updated_date", "created_date"):
|
|
61
|
+
if key in network:
|
|
62
|
+
value = network[key]
|
|
63
|
+
flat["network_{0}".format(id)] = " ".join(value) if type(value) in (list, tuple) else value
|
|
64
|
+
|
|
65
|
+
if "contacts" in parsed:
|
|
66
|
+
contacts = parsed.get("contacts")
|
|
67
|
+
if type(contacts) is list:
|
|
68
|
+
# handle IP-style contacts, which show up as a list
|
|
69
|
+
for contact in contacts:
|
|
70
|
+
contact_type = contact.get("type")
|
|
71
|
+
for key in (
|
|
72
|
+
"name",
|
|
73
|
+
"email",
|
|
74
|
+
"org",
|
|
75
|
+
"abuse_mailbos",
|
|
76
|
+
"address",
|
|
77
|
+
"street",
|
|
78
|
+
"city",
|
|
79
|
+
"state",
|
|
80
|
+
"postal",
|
|
81
|
+
"country",
|
|
82
|
+
"phone",
|
|
83
|
+
"fax",
|
|
84
|
+
):
|
|
85
|
+
if key in contact:
|
|
86
|
+
value = contact[key]
|
|
87
|
+
flat["{0}_{1}".format(contact_type, key)] = " ".join(value) if type(value) in (list, tuple) else value
|
|
88
|
+
|
|
89
|
+
elif type(contacts) is dict:
|
|
90
|
+
for contact_type in ("registrant", "admin", "tech", "billing"):
|
|
91
|
+
contact = contacts.get(contact_type, {})
|
|
92
|
+
for key in ("name", "email", "org", "street", "city", "state", "postal", "country", "phone", "fax"):
|
|
93
|
+
if key in contact:
|
|
94
|
+
value = contact[key]
|
|
95
|
+
flat["{0}_{1}".format(contact_type, key)] = " ".join(value) if type(value) in (list, tuple) else value
|
|
96
|
+
|
|
97
|
+
return flat
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ParsedDomainRdap(Results):
|
|
101
|
+
"""Returns the parsed domain rdap results in a format that can quickly be flattened"""
|
|
102
|
+
|
|
103
|
+
def flattened(self):
|
|
104
|
+
"""Returns a flattened version of the parsed domain rdap data"""
|
|
105
|
+
parsed = self["parsed_domain_rdap"]
|
|
106
|
+
flat = OrderedDict()
|
|
107
|
+
for key in (
|
|
108
|
+
"domain",
|
|
109
|
+
"handle",
|
|
110
|
+
"domain_statuses",
|
|
111
|
+
"creation_date",
|
|
112
|
+
"last_changed_date",
|
|
113
|
+
"expiration_date",
|
|
114
|
+
"dnssec",
|
|
115
|
+
"nameservers",
|
|
116
|
+
"conformance",
|
|
117
|
+
"emails",
|
|
118
|
+
"email_domains",
|
|
119
|
+
"unclassified_emails",
|
|
120
|
+
):
|
|
121
|
+
if key in parsed:
|
|
122
|
+
value = parsed[key]
|
|
123
|
+
flat[key] = " | ".join(value) if type(value) in (list, tuple) else value
|
|
124
|
+
|
|
125
|
+
registrar = parsed.get("registrar", {})
|
|
126
|
+
for registrar_key, registrar_value in registrar.items():
|
|
127
|
+
if registrar_key == "contacts":
|
|
128
|
+
for i, contact in enumerate(registrar_value, start=1):
|
|
129
|
+
for contact_key, contact_value in contact.items():
|
|
130
|
+
flat[f"registrar_contacts_{contact_key}"] = (
|
|
131
|
+
" | ".join(contact_value) if type(contact_value) in (list, tuple) else contact_value
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
continue
|
|
135
|
+
flat[f"registrar_{registrar_key}"] = registrar_value
|
|
136
|
+
|
|
137
|
+
contacts = parsed.get("contacts")
|
|
138
|
+
if contacts:
|
|
139
|
+
for i, contact in enumerate(contacts, start=1):
|
|
140
|
+
for contact_key, contact_value in contact.items():
|
|
141
|
+
flat[f"contact_{contact_key}_{i}"] = " | ".join(contact_value) if type(contact_value) in (list, tuple) else contact_value
|
|
142
|
+
|
|
143
|
+
return flat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: domaintools_api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Adds async capabilities to the base product object"""
|
|
2
|
+
|
|
2
3
|
import asyncio
|
|
3
4
|
from httpx import AsyncClient
|
|
4
5
|
|
|
@@ -6,9 +7,14 @@ from domaintools.base_results import Results
|
|
|
6
7
|
|
|
7
8
|
from domaintools.exceptions import ServiceUnavailableException, ServiceException
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
class _AIter(object):
|
|
10
12
|
"""A wrapper to wrap an AsyncResults as an async iterable"""
|
|
11
|
-
|
|
13
|
+
|
|
14
|
+
__slots__ = (
|
|
15
|
+
"results",
|
|
16
|
+
"iterator",
|
|
17
|
+
)
|
|
12
18
|
|
|
13
19
|
def __init__(self, results):
|
|
14
20
|
self.results = results
|
|
@@ -35,11 +41,11 @@ class AsyncResults(Results):
|
|
|
35
41
|
return self.__awaitable__().__await__()
|
|
36
42
|
|
|
37
43
|
async def _make_async_request(self, session):
|
|
38
|
-
if self.product in [
|
|
44
|
+
if self.product in ["iris-investigate", "iris-enrich", "iris-detect-escalate-domains"]:
|
|
39
45
|
post_data = self.kwargs.copy()
|
|
40
46
|
post_data.update(self.api.extra_request_params)
|
|
41
47
|
results = await session.post(url=self.url, data=post_data)
|
|
42
|
-
elif self.product in [
|
|
48
|
+
elif self.product in ["iris-detect-manage-watchlist-domains"]:
|
|
43
49
|
patch_data = self.kwargs.copy()
|
|
44
50
|
patch_data.update(self.api.extra_request_params)
|
|
45
51
|
results = await session.patch(url=self.url, json=patch_data)
|
|
@@ -47,7 +53,7 @@ class AsyncResults(Results):
|
|
|
47
53
|
results = await session.get(url=self.url, params=self.kwargs, **self.api.extra_request_params)
|
|
48
54
|
if results:
|
|
49
55
|
self.setStatus(results.status_code, results)
|
|
50
|
-
if self.kwargs.get(
|
|
56
|
+
if self.kwargs.get("format", "json") == "json":
|
|
51
57
|
self._data = results.json()
|
|
52
58
|
else:
|
|
53
59
|
self._data = results.text()
|
|
@@ -60,7 +66,7 @@ class AsyncResults(Results):
|
|
|
60
66
|
async def __awaitable__(self):
|
|
61
67
|
if self._data is None:
|
|
62
68
|
|
|
63
|
-
async with AsyncClient(verify=self.api.verify_ssl,
|
|
69
|
+
async with AsyncClient(verify=self.api.verify_ssl, proxy=self.api.proxy_url, timeout=None) as session:
|
|
64
70
|
wait_time = self._wait_time()
|
|
65
71
|
if wait_time is None and self.api:
|
|
66
72
|
try:
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "domaintools_api"
|
|
7
7
|
dependencies = ["httpx", "rich", "typer"]
|
|
8
|
-
requires-python = ">= 3.
|
|
8
|
+
requires-python = ">= 3.9"
|
|
9
9
|
authors = [{ name = "DomainTools", email = "integrations@domaintools.com" }]
|
|
10
10
|
description = "DomainTools Official Python API"
|
|
11
11
|
readme = "README.md"
|
|
@@ -18,11 +18,6 @@ classifiers = [
|
|
|
18
18
|
"Environment :: Console",
|
|
19
19
|
"License :: OSI Approved :: MIT License",
|
|
20
20
|
"Programming Language :: Python",
|
|
21
|
-
"Programming Language :: Python :: 3",
|
|
22
|
-
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
-
"Programming Language :: Python :: 3.6",
|
|
24
|
-
"Programming Language :: Python :: 3.7",
|
|
25
|
-
"Programming Language :: Python :: 3.8",
|
|
26
21
|
"Programming Language :: Python :: 3.9",
|
|
27
22
|
"Programming Language :: Python :: 3.10",
|
|
28
23
|
"Topic :: Software Development :: Libraries",
|
|
@@ -153,6 +153,32 @@ def test_parsed_whois():
|
|
|
153
153
|
assert isinstance(result.flattened(), dict)
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
@vcr.use_cassette
|
|
157
|
+
def test_parsed_domain_rdap():
|
|
158
|
+
api_call = api.parsed_domain_rdap("google.com")
|
|
159
|
+
with api_call as result:
|
|
160
|
+
for key in (
|
|
161
|
+
"handle",
|
|
162
|
+
"domain_statuses",
|
|
163
|
+
"creation_date",
|
|
164
|
+
"last_changed_date",
|
|
165
|
+
"expiration_date",
|
|
166
|
+
"nameservers",
|
|
167
|
+
"conformance",
|
|
168
|
+
"emails",
|
|
169
|
+
"email_domains",
|
|
170
|
+
"unclassified_emails",
|
|
171
|
+
"registrar",
|
|
172
|
+
"contacts",
|
|
173
|
+
):
|
|
174
|
+
assert key in result.get("parsed_domain_rdap")
|
|
175
|
+
|
|
176
|
+
for key, value in result.items():
|
|
177
|
+
assert key
|
|
178
|
+
|
|
179
|
+
assert isinstance(result.flattened(), dict)
|
|
180
|
+
|
|
181
|
+
|
|
156
182
|
@vcr.use_cassette
|
|
157
183
|
def test_registrant_monitor():
|
|
158
184
|
api_call = api.registrant_monitor("google")
|
|
@@ -385,7 +411,7 @@ def test_iris():
|
|
|
385
411
|
with pytest.raises(ValueError):
|
|
386
412
|
api.iris()
|
|
387
413
|
|
|
388
|
-
with api.iris(domain="google.com"
|
|
414
|
+
with api.iris(domain="google.com") as results:
|
|
389
415
|
assert results
|
|
390
416
|
for result in results:
|
|
391
417
|
assert "domain" in result
|
|
@@ -471,10 +497,10 @@ def test_iris_detect_watched_domains():
|
|
|
471
497
|
detect_results = api.iris_detect_watched_domains(
|
|
472
498
|
monitor_id="nAwmQg2pqg", sort=["risk_score"], order="desc"
|
|
473
499
|
)
|
|
474
|
-
assert len(detect_results["watchlist_domains"]) ==
|
|
500
|
+
assert len(detect_results["watchlist_domains"]) == 2
|
|
475
501
|
|
|
476
502
|
detect_results = api.iris_detect_watched_domains(escalation_types="blocked")
|
|
477
|
-
assert detect_results["count"] ==
|
|
503
|
+
assert detect_results["count"] == 1
|
|
478
504
|
|
|
479
505
|
|
|
480
506
|
@vcr.use_cassette
|
|
@@ -22,7 +22,7 @@ def test_valid_command():
|
|
|
22
22
|
user = os.environ.get("TEST_USER", "test")
|
|
23
23
|
key = os.environ.get("TEST_KEY", "key")
|
|
24
24
|
result = runner.invoke(dt_cli, ["account_information", "--help"])
|
|
25
|
-
assert "
|
|
25
|
+
assert "Provides a snapshot of your accounts current API usage." in result.stdout
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def test_invalid_command():
|
domaintools_api-2.1.0/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.1.0
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"""Defines the used Result object based on the current versions and/or features available to Python runtime
|
|
2
|
-
|
|
3
|
-
Additionally, defines any custom result objects that may be used to enable more Pythonic interaction with endpoints.
|
|
4
|
-
"""
|
|
5
|
-
from itertools import chain
|
|
6
|
-
|
|
7
|
-
try: # pragma: no cover
|
|
8
|
-
from collections import OrderedDict
|
|
9
|
-
except ImportError: # pragma: no cover
|
|
10
|
-
from ordereddict import OrderedDict
|
|
11
|
-
|
|
12
|
-
from itertools import zip_longest
|
|
13
|
-
from domaintools_async import AsyncResults as Results
|
|
14
|
-
|
|
15
|
-
class Reputation(Results):
|
|
16
|
-
"""Returns the reputation results in a format that can quickly be converted into floats / ints"""
|
|
17
|
-
|
|
18
|
-
def __float__(self):
|
|
19
|
-
return float(self['risk_score'])
|
|
20
|
-
|
|
21
|
-
def __int__(self):
|
|
22
|
-
return int(self['risk_score'])
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class GroupedIterable(Results):
|
|
26
|
-
"""Returns a results item in a format that allows for grouped iteration of mulpitle result lists"""
|
|
27
|
-
|
|
28
|
-
def _items(self):
|
|
29
|
-
if self._items_list is None:
|
|
30
|
-
self._items_list = chain(*[zip_longest([], value, fillvalue=key) for key, value in self.response().items()
|
|
31
|
-
if type(value) in (list, tuple)])
|
|
32
|
-
|
|
33
|
-
return self._items_list
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class ParsedWhois(Results):
|
|
37
|
-
"""Returns the parsed whois results in a format that can quickly be flattened"""
|
|
38
|
-
|
|
39
|
-
def flattened(self):
|
|
40
|
-
"""Returns a flattened version of the parsed whois data"""
|
|
41
|
-
parsed = self['parsed_whois']
|
|
42
|
-
flat = OrderedDict()
|
|
43
|
-
for key in ('domain', 'created_date', 'updated_date', 'expired_date', 'statuses', 'name_servers'):
|
|
44
|
-
if key in parsed:
|
|
45
|
-
value = parsed[key]
|
|
46
|
-
flat[key] = ' | '.join(value) if type(value) in (list, tuple) else value
|
|
47
|
-
|
|
48
|
-
registrar = parsed.get('registrar', {})
|
|
49
|
-
for key in ('name', 'abuse_contact_phone', 'abuse_contact_email', 'iana_id', 'url', 'whois_server'):
|
|
50
|
-
if key in registrar:
|
|
51
|
-
flat['registrar_{0}'.format(key)] = registrar[key]
|
|
52
|
-
|
|
53
|
-
if "networks" in parsed:
|
|
54
|
-
networks = parsed.get("networks")
|
|
55
|
-
for network in networks:
|
|
56
|
-
id = network.get("id")
|
|
57
|
-
for key in ('range', 'asn', 'org', 'parent', 'customer', 'country', 'phone', 'status', 'source', 'updated_date', 'created_date'):
|
|
58
|
-
if key in network:
|
|
59
|
-
value = network[key]
|
|
60
|
-
flat['network_{0}'.format(id)] = ' '.join(value) if type(value) in (list, tuple) else value
|
|
61
|
-
|
|
62
|
-
if "contacts" in parsed:
|
|
63
|
-
contacts = parsed.get("contacts")
|
|
64
|
-
if type(contacts) is list:
|
|
65
|
-
# handle IP-style contacts, which show up as a list
|
|
66
|
-
for contact in contacts:
|
|
67
|
-
contact_type = contact.get("type")
|
|
68
|
-
for key in ('name', 'email', 'org', 'abuse_mailbos', 'address', 'street', 'city', 'state', 'postal', 'country', 'phone', 'fax'):
|
|
69
|
-
if key in contact:
|
|
70
|
-
value = contact[key]
|
|
71
|
-
flat['{0}_{1}'.format(contact_type, key)] = ' '.join(value) if type(value) in (list, tuple) else value
|
|
72
|
-
|
|
73
|
-
elif type(contacts) is dict:
|
|
74
|
-
for contact_type in ('registrant', 'admin', 'tech', 'billing'):
|
|
75
|
-
contact = contacts.get(contact_type, {})
|
|
76
|
-
for key in ('name', 'email', 'org', 'street', 'city', 'state', 'postal', 'country', 'phone', 'fax'):
|
|
77
|
-
if key in contact:
|
|
78
|
-
value = contact[key]
|
|
79
|
-
flat['{0}_{1}'.format(contact_type, key)] = ' '.join(value) if type(value) in (list, tuple) else value
|
|
80
|
-
|
|
81
|
-
return flat
|
|
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
|
|
File without changes
|
|
File without changes
|
{domaintools_api-2.1.0 → domaintools_api-2.2.0}/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
|