Skip to content

Commit 95ae2cc

Browse files
committed
IDEV-1995: Add domainrdap cli
1 parent 11829cd commit 95ae2cc

File tree

4 files changed

+79
-27
lines changed

4 files changed

+79
-27
lines changed

domaintools/cli/api.py

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import _io
66

7+
from datetime import datetime
78
from typing import Optional, Dict, Tuple
89
from rich.progress import Progress, SpinnerColumn, TextColumn
910

@@ -28,11 +29,21 @@ def print_api_version(value: bool):
2829
def validate_format_input(value: str):
2930
VALID_FORMATS = ("list", "json", "xml", "html")
3031
if value not in VALID_FORMATS:
31-
raise typer.BadParameter(
32-
f"{value} is not in available formats: {VALID_FORMATS}"
33-
)
32+
raise typer.BadParameter(f"{value} is not in available formats: {VALID_FORMATS}")
3433
return value
3534

35+
@staticmethod
36+
def validate_after_or_before_input(value: str):
37+
if value is None or value.replace("-", "").isdigit():
38+
return value
39+
40+
# Checks if value is a valid ISO 8601 datetime string in UTC form
41+
try:
42+
datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
43+
return value
44+
except:
45+
raise typer.BadParameter(f"{value} is neither an integer or a valid ISO 8601 datetime string in UTC form")
46+
3647
@staticmethod
3748
def validate_source_file_extension(value: str):
3849
"""Validates source file extension.
@@ -51,9 +62,7 @@ def validate_source_file_extension(value: str):
5162
ext = get_file_extension(value)
5263

5364
if ext.lower() not in VALID_EXTENSIONS:
54-
raise typer.BadParameter(
55-
f"{value} is not in valid extensions. Valid file extensions: {VALID_EXTENSIONS}"
56-
)
65+
raise typer.BadParameter(f"{value} is not in valid extensions. Valid file extensions: {VALID_EXTENSIONS}")
5766

5867
return value
5968

@@ -85,11 +94,7 @@ def args_to_dict(*args) -> Dict:
8594
def _get_formatted_output(cls, cmd_name: str, response, out_format: str = "json"):
8695
if cmd_name in ("available_api_calls",):
8796
return "\n".join(response)
88-
return str(
89-
getattr(response, out_format)
90-
if out_format != "list"
91-
else response.as_list()
92-
)
97+
return str(getattr(response, out_format) if out_format != "list" else response.as_list())
9398

9499
@classmethod
95100
def _get_credentials(cls, params: Optional[Dict] = {}) -> Tuple[str]:
@@ -106,9 +111,7 @@ def _get_credentials(cls, params: Optional[Dict] = {}) -> Tuple[str]:
106111
with open(creds_file, "r") as cf:
107112
user, key = cf.readline().strip(), cf.readline().strip()
108113
except FileNotFoundError as e:
109-
raise typer.BadParameter(
110-
f"{str(e)}. Please create one first and try again."
111-
)
114+
raise typer.BadParameter(f"{str(e)}. Please create one first and try again.")
112115

113116
return user, key
114117

@@ -198,9 +201,7 @@ def run(cls, name: str, params: Optional[Dict] = {}, **kwargs):
198201
total=None,
199202
)
200203

201-
output = cls._get_formatted_output(
202-
cmd_name=name, response=response, out_format=response_format
203-
)
204+
output = cls._get_formatted_output(cmd_name=name, response=response, out_format=response_format)
204205

205206
if isinstance(out_file, _io.TextIOWrapper):
206207
# use rich `print` command to prettify the ouput in sys.stdout
@@ -215,10 +216,7 @@ def run(cls, name: str, params: Optional[Dict] = {}, **kwargs):
215216
_reason = getattr(e, "reason", {})
216217
# check data type first as some of the reasons is just plain text
217218
if isinstance(_reason, dict):
218-
_reason = (
219-
_reason.get("error", {}).get("message")
220-
or "Unknown Error occured."
221-
)
219+
_reason = _reason.get("error", {}).get("message") or "Unknown Error occured."
222220

223221
reason = typer.style(_reason, bg=typer.colors.RED)
224222

domaintools/cli/commands/feeds.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ def feeds_nad(
3535
help="Output format in {'list', 'json', 'xml', 'html'}",
3636
callback=DTCLICommand.validate_format_input,
3737
),
38-
out_file: typer.FileTextWrite = typer.Option(
39-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
40-
),
38+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
4139
no_verify_ssl: bool = typer.Option(
4240
False,
4341
"--no-verify-ssl",
@@ -100,9 +98,7 @@ def feeds_nod(
10098
help="Output format in {'list', 'json', 'xml', 'html'}",
10199
callback=DTCLICommand.validate_format_input,
102100
),
103-
out_file: typer.FileTextWrite = typer.Option(
104-
sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"
105-
),
101+
out_file: typer.FileTextWrite = typer.Option(sys.stdout, "-o", "--out-file", help="Output file (defaults to stdout)"),
106102
no_verify_ssl: bool = typer.Option(
107103
False,
108104
"--no-verify-ssl",
@@ -136,3 +132,59 @@ def feeds_nod(
136132
),
137133
):
138134
DTCLICommand.run(name=c.FEEDS_NOD, params=ctx.params)
135+
136+
137+
@dt_cli.command(
138+
name=c.FEEDS_DOMAINRDAP,
139+
help=get_cli_helptext_by_name(command_name=c.FEEDS_DOMAINRDAP),
140+
)
141+
def feeds_domainrdap(
142+
ctx: typer.Context,
143+
user: str = typer.Option(None, "-u", "--user", help="Domaintools API Username."),
144+
key: str = typer.Option(None, "-k", "--key", help="DomainTools API key"),
145+
creds_file: str = typer.Option(
146+
"~/.dtapi",
147+
"-c",
148+
"--credfile",
149+
help="Optional file with API username and API key, one per line.",
150+
),
151+
no_verify_ssl: bool = typer.Option(
152+
False,
153+
"--no-verify-ssl",
154+
help="Skip verification of SSL certificate when making HTTPs API calls",
155+
),
156+
no_sign_api_key: bool = typer.Option(
157+
False,
158+
"--no-sign-api-key",
159+
help="Skip signing of api key",
160+
),
161+
sessionID: str = typer.Option(
162+
None,
163+
"--session-id",
164+
help="Unique identifier for the session",
165+
),
166+
after: str = typer.Option(
167+
None,
168+
"--after",
169+
help="Start of the time window, relative to the current time in seconds, for which data will be provided",
170+
callback=DTCLICommand.validate_after_or_before_input,
171+
),
172+
before: str = typer.Option(
173+
None,
174+
"--before",
175+
help="The end of the query window in seconds, relative to the current time, inclusive",
176+
callback=DTCLICommand.validate_after_or_before_input,
177+
),
178+
domain: str = typer.Option(
179+
None,
180+
"-d",
181+
"--domain",
182+
help="A string value used to filter feed results",
183+
),
184+
top: str = typer.Option(
185+
None,
186+
"--top",
187+
help="Number of results to return in the response payload",
188+
),
189+
):
190+
DTCLICommand.run(name=c.FEEDS_DOMAINRDAP, params=ctx.params)

domaintools/cli/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@
4646
# feeds
4747
FEEDS_NAD = "nad"
4848
FEEDS_NOD = "nod"
49+
FEEDS_DOMAINRDAP = "domainrdap"

domaintools/cli/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def _phisheye_termlist():
8484
c.IRIS_DETECT_IGNORED_DOMAINS: "Returns back a list of ignored domains in Iris Detect based on the provided filters.",
8585
c.FEEDS_NAD: "Returns back newly active domains feed.",
8686
c.FEEDS_NOD: "Returns back newly observed domains feed.",
87+
c.FEEDS_DOMAINRDAP: "Returns changes to global domain registration information, populated by the Registration Data Access Protocol (RDAP).",
8788
}
8889

8990

0 commit comments

Comments
 (0)