Skip to content

Commit c638c85

Browse files
authored
Merge pull request #171 from DomainTools/2.5
Release v.2.5
2 parents 9112f67 + f173171 commit c638c85

File tree

59 files changed

+102487
-419557
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+102487
-419557
lines changed

.github/workflows/test-build-publish.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,38 @@ jobs:
2929
export TOX_SKIP_MISSING_INTERPRETERS="False";
3030
tox -e py
3131
32+
e2e-tests:
33+
runs-on: ubuntu-latest
34+
env:
35+
MITM_BASIC_AUTH_CONTAINER_NAME: e2e_test_mitm_basic_auth
36+
MITM_CUSTOM_CERT_CONTAINER_NAME: e2e_test_mitm_custom_cert
37+
DOCKER_NETWORK_NAME: e2e_test_docker_network
38+
TEST_USER: integrations_testing
39+
TEST_KEY: ${{ secrets.TEST_KEY }}
40+
41+
steps:
42+
- name: Checkout code
43+
uses: actions/checkout@v3
44+
45+
- name: Install dependencies
46+
run: |
47+
python -m pip install --upgrade pip
48+
pip install -e .
49+
pip install -r ./requirements/development.txt
50+
51+
- name: Setup E2E environment
52+
run: |
53+
sh ./tests/e2e/scripts/setup_e2e.sh
54+
55+
- name: Run E2E tests
56+
run: |
57+
python -m pytest -s --capture=sys -v --cov=domaintools tests/e2e
58+
59+
- name: Cleanup E2E environment
60+
if: '!cancelled()'
61+
run: |
62+
sh ./tests/e2e/scripts/cleanup_e2e.sh
63+
3264
# run only in main and in pull request to `main` and in publish release
3365
release-build:
3466
if: |

README.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,44 @@ results = api.nod(sessionID="my-session-id", after=-7200)
270270

271271
for partial_result in results.response() # generator that holds NOD feeds data for the past 2 hours and is expected to request multiple times
272272
# do things to partial_result
273-
```
273+
```
274+
275+
276+
Running E2E Tests Locally
277+
===================
278+
For now, e2e tests only covers proxy and ssl testing. We are expected to broaden our e2e tests to other scenarios moving forward.
279+
To add more e2e tests, put these in the `../tests/e2e` folder.
280+
281+
## Preparation
282+
- Create virtual environment.
283+
```bash
284+
python3 -m venv venv
285+
```
286+
287+
- Activate virtual environment
288+
```bash
289+
source venv/bin/activate
290+
```
291+
292+
- Install dependencies.
293+
```bash
294+
pip install -r requirements/development.txt
295+
```
296+
297+
- From the python_api project root directory, install the package.
298+
```bash
299+
pip install -e .
300+
```
301+
302+
- Export api credentials to use.
303+
```bash
304+
export TEST_USER=<user-key>
305+
export TEST_KEY=<api-key>
306+
```
307+
308+
## Run the end-to-end test script
309+
- Before running the test, be sure that docker is running.
310+
- Execute the e2e test script .
311+
```bash
312+
sh tests/e2e/scripts/test_e2e_runner.sh
313+
```

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.4.1
1+
2.5.0

domaintools/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@
2020
2121
"""
2222

23-
current = "2.4.1"
23+
current = "2.5.0"

domaintools/api.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from datetime import datetime, timedelta, timezone
22
from hashlib import sha1, sha256
33
from hmac import new as hmac
4+
from typing import Union
45

56
import re
7+
import ssl
68

79
from domaintools.constants import Endpoint, ENDPOINT_TO_SOURCE_MAP, FEEDS_PRODUCTS_LIST, OutputFormat
810
from domaintools._version import current as version
@@ -76,7 +78,7 @@ def __init__(
7678
self.username = username
7779
self.key = key
7880
self.https = https
79-
self.verify_ssl = verify_ssl
81+
self.verify_ssl = self._get_ssl_default_context(verify_ssl)
8082
self.rate_limit = rate_limit
8183
self.proxy_url = proxy_url
8284
self.extra_request_params = {}
@@ -92,6 +94,9 @@ def __init__(
9294
if proxy_url and not isinstance(proxy_url, str):
9395
raise Exception("Proxy URL must be a string. For example: '127.0.0.1:8888'")
9496

97+
def _get_ssl_default_context(self, verify_ssl: Union[str, bool]):
98+
return ssl.create_default_context(cafile=verify_ssl) if isinstance(verify_ssl, str) else verify_ssl
99+
95100
def _build_api_url(self, api_url=None, api_port=None):
96101
"""Build the API url based on the given url and port. Defaults to `https://api.domaintools.com`"""
97102
rest_api_url = "https://api.domaintools.com"
@@ -1187,3 +1192,65 @@ def noh(self, **kwargs) -> FeedsResults:
11871192
cls=FeedsResults,
11881193
**kwargs,
11891194
)
1195+
1196+
def realtime_domain_risk(self, **kwargs) -> FeedsResults:
1197+
"""Returns back list of the realtime domain risk feed.
1198+
Contains realtime domain risk information for apex-level domains, regardless of observed traffic.
1199+
1200+
domain: str: Filter for an exact domain or a substring contained within a domain by prefixing or suffixing your substring with "*". Check the documentation for examples
1201+
1202+
before: str: Filter for records before the given time value inclusive or time offset relative to now
1203+
1204+
after: str: Filter for records after the given time value inclusive or time offset relative to now
1205+
1206+
headers: bool: Use in combination with Accept: text/csv headers to control if headers are sent or not
1207+
1208+
sessionID: str: A custom string to distinguish between different sessions
1209+
1210+
top: int: Limit the number of results to the top N, where N is the value of this parameter.
1211+
"""
1212+
validate_feeds_parameters(kwargs)
1213+
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
1214+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint).value
1215+
if endpoint == Endpoint.DOWNLOAD.value or kwargs.get("output_format", OutputFormat.JSONL.value) != OutputFormat.CSV.value:
1216+
# headers param is allowed only in Feed API and CSV format
1217+
kwargs.pop("headers", None)
1218+
1219+
return self._results(
1220+
f"domain-risk-({source})",
1221+
f"v1/{endpoint}/domainrisk/",
1222+
response_path=(),
1223+
cls=FeedsResults,
1224+
**kwargs,
1225+
)
1226+
1227+
def domainhotlist(self, **kwargs) -> FeedsResults:
1228+
"""Returns back list of domain hotlist feed.
1229+
Contains high-risk, apex-level domains that are observed by DomainTools' global sensor network to be active within 24 hours.
1230+
1231+
domain: str: Filter for an exact domain or a substring contained within a domain by prefixing or suffixing your substring with "*". Check the documentation for examples
1232+
1233+
before: str: Filter for records before the given time value inclusive or time offset relative to now
1234+
1235+
after: str: Filter for records after the given time value inclusive or time offset relative to now
1236+
1237+
headers: bool: Use in combination with Accept: text/csv headers to control if headers are sent or not
1238+
1239+
sessionID: str: A custom string to distinguish between different sessions
1240+
1241+
top: int: Limit the number of results to the top N, where N is the value of this parameter.
1242+
"""
1243+
validate_feeds_parameters(kwargs)
1244+
endpoint = kwargs.pop("endpoint", Endpoint.FEED.value)
1245+
source = ENDPOINT_TO_SOURCE_MAP.get(endpoint).value
1246+
if endpoint == Endpoint.DOWNLOAD.value or kwargs.get("output_format", OutputFormat.JSONL.value) != OutputFormat.CSV.value:
1247+
# headers param is allowed only in Feed API and CSV format
1248+
kwargs.pop("headers", None)
1249+
1250+
return self._results(
1251+
f"domain-hotlist-feed-({source})",
1252+
f"v1/{endpoint}/domainhotlist/",
1253+
response_path=(),
1254+
cls=FeedsResults,
1255+
**kwargs,
1256+
)

domaintools/cli/commands/feeds.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,163 @@ def feeds_noh(
394394
),
395395
):
396396
DTCLICommand.run(name=c.FEEDS_NOH, params=ctx.params)
397+
398+
399+
@dt_cli.command(
400+
name=c.FEEDS_DOMAINHOTLIST,
401+
help=get_cli_helptext_by_name(command_name=c.FEEDS_DOMAINHOTLIST),
402+
)
403+
def feeds_domainhotlist(
404+
ctx: typer.Context,
405+
user: str = typer.Option(None, "-u", "--user", help="Domaintools API Username."),
406+
key: str = typer.Option(None, "-k", "--key", help="DomainTools API key"),
407+
creds_file: str = typer.Option(
408+
"~/.dtapi",
409+
"-c",
410+
"--credfile",
411+
help="Optional file with API username and API key, one per line.",
412+
),
413+
no_verify_ssl: bool = typer.Option(
414+
False,
415+
"--no-verify-ssl",
416+
help="Skip verification of SSL certificate when making HTTPs API calls",
417+
),
418+
no_sign_api_key: bool = typer.Option(
419+
False,
420+
"--no-sign-api-key",
421+
help="Skip signing of api key",
422+
),
423+
header_authentication: bool = typer.Option(
424+
True,
425+
"--no-header-auth",
426+
help="Don't use header authentication",
427+
),
428+
output_format: str = typer.Option(
429+
"jsonl",
430+
"-f",
431+
"--format",
432+
help=f"Output format in [{OutputFormat.JSONL.value}, {OutputFormat.CSV.value}]",
433+
callback=DTCLICommand.validate_feeds_format_input,
434+
),
435+
endpoint: str = typer.Option(
436+
Endpoint.FEED.value,
437+
"-e",
438+
"--endpoint",
439+
help=f"Valid endpoints: [{Endpoint.FEED.value}, {Endpoint.DOWNLOAD.value}]",
440+
callback=DTCLICommand.validate_endpoint_input,
441+
),
442+
sessionID: str = typer.Option(
443+
None,
444+
"--session-id",
445+
help="Unique identifier for the session",
446+
),
447+
after: str = typer.Option(
448+
None,
449+
"--after",
450+
help="Start of the time window, relative to the current time in seconds, for which data will be provided",
451+
callback=DTCLICommand.validate_after_or_before_input,
452+
),
453+
before: str = typer.Option(
454+
None,
455+
"--before",
456+
help="The end of the query window in seconds, relative to the current time, inclusive",
457+
callback=DTCLICommand.validate_after_or_before_input,
458+
),
459+
domain: str = typer.Option(
460+
None,
461+
"-d",
462+
"--domain",
463+
help="A string value used to filter feed results",
464+
),
465+
headers: bool = typer.Option(
466+
False,
467+
"--headers",
468+
help="Adds a header to the first line of response when text/csv is set in header parameters",
469+
),
470+
top: str = typer.Option(
471+
None,
472+
"--top",
473+
help="Number of results to return in the response payload. This is ignored in download endpoint",
474+
),
475+
):
476+
DTCLICommand.run(name=c.FEEDS_DOMAINHOTLIST, params=ctx.params)
477+
478+
479+
@dt_cli.command(
480+
name=c.FEEDS_REALTIME_DOMAIN_RISK,
481+
help=get_cli_helptext_by_name(command_name=c.FEEDS_REALTIME_DOMAIN_RISK),
482+
)
483+
def feeds_realtime_domain_risk(
484+
ctx: typer.Context,
485+
user: str = typer.Option(None, "-u", "--user", help="Domaintools API Username."),
486+
key: str = typer.Option(None, "-k", "--key", help="DomainTools API key"),
487+
creds_file: str = typer.Option(
488+
"~/.dtapi",
489+
"-c",
490+
"--credfile",
491+
help="Optional file with API username and API key, one per line.",
492+
),
493+
no_verify_ssl: bool = typer.Option(
494+
False,
495+
"--no-verify-ssl",
496+
help="Skip verification of SSL certificate when making HTTPs API calls",
497+
),
498+
no_sign_api_key: bool = typer.Option(
499+
False,
500+
"--no-sign-api-key",
501+
help="Skip signing of api key",
502+
),
503+
header_authentication: bool = typer.Option(
504+
True,
505+
"--no-header-auth",
506+
help="Don't use header authentication",
507+
),
508+
output_format: str = typer.Option(
509+
"jsonl",
510+
"-f",
511+
"--format",
512+
help=f"Output format in [{OutputFormat.JSONL.value}, {OutputFormat.CSV.value}]",
513+
callback=DTCLICommand.validate_feeds_format_input,
514+
),
515+
endpoint: str = typer.Option(
516+
Endpoint.FEED.value,
517+
"-e",
518+
"--endpoint",
519+
help=f"Valid endpoints: [{Endpoint.FEED.value}, {Endpoint.DOWNLOAD.value}]",
520+
callback=DTCLICommand.validate_endpoint_input,
521+
),
522+
sessionID: str = typer.Option(
523+
None,
524+
"--session-id",
525+
help="Unique identifier for the session",
526+
),
527+
after: str = typer.Option(
528+
None,
529+
"--after",
530+
help="Start of the time window, relative to the current time in seconds, for which data will be provided",
531+
callback=DTCLICommand.validate_after_or_before_input,
532+
),
533+
before: str = typer.Option(
534+
None,
535+
"--before",
536+
help="The end of the query window in seconds, relative to the current time, inclusive",
537+
callback=DTCLICommand.validate_after_or_before_input,
538+
),
539+
domain: str = typer.Option(
540+
None,
541+
"-d",
542+
"--domain",
543+
help="A string value used to filter feed results",
544+
),
545+
headers: bool = typer.Option(
546+
False,
547+
"--headers",
548+
help="Adds a header to the first line of response when text/csv is set in header parameters",
549+
),
550+
top: str = typer.Option(
551+
None,
552+
"--top",
553+
help="Number of results to return in the response payload. This is ignored in download endpoint",
554+
),
555+
):
556+
DTCLICommand.run(name=c.FEEDS_REALTIME_DOMAIN_RISK, params=ctx.params)

domaintools/cli/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,7 @@
4343
FEEDS_NAD = "nad"
4444
FEEDS_NOD = "nod"
4545
FEEDS_NOH = "noh"
46+
FEEDS_DOMAINHOTLIST = "domainhotlist"
4647
FEEDS_DOMAINRDAP = "domainrdap"
4748
FEEDS_DOMAINDISCOVERY = "domaindiscovery"
49+
FEEDS_REALTIME_DOMAIN_RISK = "realtime_domain_risk"

domaintools/cli/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ def _iris_investigate_helptext():
6464
c.FEEDS_NAD: "Returns back newly active domains feed.",
6565
c.FEEDS_NOD: "Returns back newly observed domains feed.",
6666
c.FEEDS_NOH: "Returns back newly observed hosts feed.",
67+
c.FEEDS_DOMAINHOTLIST: "Returns domaint hotlist feed.",
6768
c.FEEDS_DOMAINRDAP: "Returns changes to global domain registration information, populated by the Registration Data Access Protocol (RDAP).",
6869
c.FEEDS_DOMAINDISCOVERY: "Returns new domains as they are either discovered in domain registration information, observed by our global sensor network, or reported by trusted third parties.",
70+
c.FEEDS_REALTIME_DOMAIN_RISK: "Returns realtime domain risk information for apex-level domains, regardless of observed traffic.",
6971
}
7072

7173

domaintools/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ class OutputFormat(Enum):
3030
"newly-observed-domains-feed-(s3)",
3131
"newly-observed-hosts-feed-(api)",
3232
"newly-observed-hosts-feed-(s3)",
33+
"domain-hotlist-feed-(api)",
34+
"domain-hotlist-feed-(s3)",
3335
"domain-registration-data-access-protocol-feed-(api)",
3436
"domain-registration-data-access-protocol-feed-(s3)",
37+
"domain-risk-feed-(api)",
38+
"domain-risk-feed-(s3)",
3539
"real-time-domain-discovery-feed-(api)",
3640
"real-time-domain-discovery-feed-(s3)",
3741
]

requirements/development.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ ipython==8.1.1
77
wheel==0.37.1
88
python-coveralls==2.9.3
99
vcrpy==4.1.1
10+
urllib3==1.26.15

0 commit comments

Comments
 (0)