Skip to content

Commit 0a37f96

Browse files
Merge pull request #175 from censys/adh/fix-cli-colors
Fix CLI Colors
2 parents f798ffd + 4f14897 commit 0a37f96

File tree

12 files changed

+142
-97
lines changed

12 files changed

+142
-97
lines changed

.github/workflows/python-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ name: Python CI
22

33
on:
44
push:
5+
branches:
6+
- main
57
paths-ignore:
68
- "**.rst"
79
- "**.md"

censys/cli/commands/account.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import argparse
33
import sys
44

5-
from rich import box, print, print_json
5+
from rich import box
66
from rich.table import Table
77

8+
from censys.cli.utils import console
89
from censys.common.exceptions import CensysUnauthorizedException
910
from censys.search.v2.api import CensysSearchAPIv2
1011

@@ -19,20 +20,25 @@ def cli_account(args: argparse.Namespace): # pragma: no cover
1920
client = CensysSearchAPIv2(args.api_id, args.api_secret)
2021
account = client.account()
2122
if args.json:
22-
print_json(data=account)
23+
console.print_json(data=account)
2324
else:
24-
table = Table("Key", "Value", show_header=False, box=box.SQUARE)
25+
table = Table(
26+
"Key", "Value", show_header=False, box=box.SQUARE, highlight=True
27+
)
2528
table.add_row("Email", account["email"])
2629
table.add_row("Login ID", account["login"])
2730
table.add_row("First Login", account["first_login"])
2831
table.add_row("Last Login", account["last_login"][:-7])
2932
quota = account["quota"]
30-
table.add_row("Query Quota", f"{quota['used']} / {quota['allowance']}")
33+
table.add_row(
34+
"Query Quota",
35+
f"{quota['used']} / {quota['allowance']} ({quota['used']/quota['allowance']:.2f}%)",
36+
)
3137
table.add_row("Quota Resets At", quota["resets_at"])
32-
print(table)
38+
console.print(table)
3339
sys.exit(0)
3440
except CensysUnauthorizedException:
35-
print("Failed to authenticate")
41+
console.print("Failed to authenticate")
3642
sys.exit(1)
3743

3844

censys/cli/commands/asm.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import json
44
import sys
55

6-
from rich.prompt import Prompt
6+
from rich.prompt import Confirm, Prompt
77

88
from censys.asm.seeds import SEED_TYPES, Seeds
9+
from censys.cli.utils import console
910
from censys.common.config import DEFAULT, get_config, write_config
1011
from censys.common.exceptions import CensysUnauthorizedException
1112

@@ -26,21 +27,26 @@ def cli_asm_config(_: argparse.Namespace): # pragma: no cover
2627
redacted_api_key = api_key.replace(api_key[:key_len], key_len * "*")
2728
api_key_prompt = f"{api_key_prompt} [cyan]({redacted_api_key})[/cyan]"
2829

29-
api_key = Prompt.ask(api_key_prompt) or api_key
30+
api_key = Prompt.ask(api_key_prompt, console=console) or api_key
3031

3132
if not api_key:
32-
print("Please enter valid credentials")
33+
console.print("Please enter valid credentials")
3334
sys.exit(1)
3435

36+
color = Confirm.ask(
37+
"Do you want color output?", default=True, show_default=False, console=console
38+
)
39+
config.set(DEFAULT, "color", "auto" if color else "")
40+
3541
try:
3642
# Assumes that login was successfully
3743
config.set(DEFAULT, "asm_api_key", api_key)
3844

3945
write_config(config)
40-
print("\nSuccessfully configured credentials")
46+
console.print("\nSuccessfully configured credentials")
4147
sys.exit(0)
4248
except CensysUnauthorizedException:
43-
print("Failed to authenticate")
49+
console.print("Failed to authenticate")
4450
sys.exit(1)
4551

4652

@@ -61,7 +67,7 @@ def cli_add_seeds(args: argparse.Namespace):
6167
try:
6268
seeds = json.loads(data)
6369
except json.decoder.JSONDecodeError as e:
64-
print(f"Invalid json {e}")
70+
console.print(f"Invalid json {e}")
6571
sys.exit(1)
6672

6773
seeds_to_add = []
@@ -72,7 +78,7 @@ def cli_add_seeds(args: argparse.Namespace):
7278
elif isinstance(seed, str):
7379
seed = {"value": seed, "type": args.default_type}
7480
else:
75-
print(f"Invalid seed {seed}")
81+
console.print(f"Invalid seed {seed}")
7682
sys.exit(1)
7783
if "label" not in seed:
7884
seed["label"] = args.label_all
@@ -84,20 +90,20 @@ def cli_add_seeds(args: argparse.Namespace):
8490
added_seeds = res["addedSeeds"]
8591
added_count = len(added_seeds)
8692
if not added_count:
87-
print("No seeds were added. (Run with -v to get more info)")
93+
console.print("No seeds were added. (Run with -v to get more info)")
8894
if not args.verbose:
8995
sys.exit(1)
9096
else:
91-
print(f"Added {added_count} seeds.")
97+
console.print(f"Added {added_count} seeds.")
9298
if added_count < to_add_count:
93-
print(f"Seeds not added: {to_add_count - added_count}")
99+
console.print(f"Seeds not added: {to_add_count - added_count}")
94100
if args.verbose: # pragma: no cover
95-
print(
101+
console.print(
96102
"The following seed(s) were not able to be added as they already exist or are reserved."
97103
)
98104
for seed in seeds_to_add:
99105
if not any([s for s in added_seeds if seed["value"] == s["value"]]):
100-
print(json.dumps(seed, indent=4))
106+
console.print_json(seed)
101107

102108

103109
def include(parent_parser: argparse._SubParsersAction, parents: dict):

censys/cli/commands/config.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import os
44
import sys
55

6-
from rich.prompt import Prompt
6+
from rich.prompt import Confirm, Prompt
77

8+
from censys.cli.utils import console
89
from censys.common.config import DEFAULT, get_config, write_config
910
from censys.common.exceptions import CensysUnauthorizedException
1011
from censys.search.v2.api import CensysSearchAPIv2
@@ -27,7 +28,7 @@ def cli_config(_: argparse.Namespace): # pragma: no cover
2728
api_secret_env = os.getenv("CENSYS_API_SECRET")
2829

2930
if api_id_env is not None or api_secret_env is not None:
30-
print(
31+
console.print(
3132
"Please note environment variables (CENSYS_API_ID & CENSYS_API_SECRET) "
3233
"will take priority over configured credentials."
3334
)
@@ -40,16 +41,21 @@ def cli_config(_: argparse.Namespace): # pragma: no cover
4041
api_id_prompt = f"{api_id_prompt} [cyan]({redacted_id})[/cyan]"
4142
api_secret_prompt = f"{api_secret_prompt} [cyan]({redacted_secret})[/cyan]"
4243

43-
api_id = Prompt.ask(api_id_prompt) or api_id
44-
api_secret = Prompt.ask(api_secret_prompt) or api_secret
44+
api_id = Prompt.ask(api_id_prompt, console=console) or api_id
45+
api_secret = Prompt.ask(api_secret_prompt, console=console) or api_secret
4546

4647
if not (api_id and api_secret):
47-
print("Please enter valid credentials")
48+
console.print("Please enter valid credentials")
4849
sys.exit(1)
4950

5051
api_id = api_id.strip()
5152
api_secret = api_secret.strip()
5253

54+
color = Confirm.ask(
55+
"Do you want color output?", default=True, show_default=False, console=console
56+
)
57+
config.set(DEFAULT, "color", "auto" if color else "")
58+
5359
try:
5460
client = CensysSearchAPIv2(api_id, api_secret)
5561
account = client.account()
@@ -60,10 +66,10 @@ def cli_config(_: argparse.Namespace): # pragma: no cover
6066
config.set(DEFAULT, "api_secret", api_secret)
6167

6268
write_config(config)
63-
print(f"\nSuccessfully authenticated for {email}")
69+
console.print(f"\nSuccessfully authenticated for {email}")
6470
sys.exit(0)
6571
except CensysUnauthorizedException:
66-
print("Failed to authenticate")
72+
console.print("Failed to authenticate")
6773
sys.exit(1)
6874

6975

censys/cli/commands/hnri.py

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
import argparse
33
import sys
44
import webbrowser
5-
from typing import List, Optional, Tuple
5+
from typing import Any, List, Optional, Tuple
66

77
import requests
8-
from rich import print
8+
from rich import box
9+
from rich.table import Table
910

11+
from censys.cli.utils import console
1012
from censys.common.exceptions import CensysCLIException, CensysNotFoundException
1113
from censys.search import CensysHosts
1214

@@ -50,76 +52,87 @@ def translate_risk(self, services: List[dict]) -> Tuple[List[dict], List[dict]]:
5052
medium_risk = []
5153

5254
for service in services:
53-
port = service.get("port")
54-
protocol = service.get("service_name")
55-
string = f"{protocol} on {port}"
56-
if protocol in self.HIGH_RISK_DEFINITION:
57-
high_risk.append({"port": port, "protocol": protocol, "string": string})
58-
elif protocol in self.MEDIUM_RISK_DEFINITION:
59-
medium_risk.append(
60-
{"port": port, "protocol": protocol, "string": string}
61-
)
55+
service_name = service.get("service_name")
56+
if service_name in self.HIGH_RISK_DEFINITION:
57+
high_risk.append(service)
58+
elif service_name in self.MEDIUM_RISK_DEFINITION:
59+
medium_risk.append(service)
6260
else:
63-
medium_risk.append(
64-
{"port": port, "protocol": protocol, "string": string}
65-
)
61+
medium_risk.append(service)
6662

6763
return high_risk, medium_risk
6864

69-
@staticmethod
70-
def risks_to_string(high_risk: list, medium_risk: list) -> str:
65+
def make_risks_into_table(self, title: str, risks: List[dict]) -> Table:
66+
"""Creates a table of risks.
67+
68+
Args:
69+
title (str): Title of the table.
70+
risks (list): List of risks.
71+
72+
Returns:
73+
Table: Table of risks.
74+
"""
75+
table = Table("Port", "Service Name", title=title, box=box.SQUARE)
76+
for risk in risks:
77+
table.add_row(str(risk.get("port")), risk.get("service_name"))
78+
return table
79+
80+
def risks_to_string(self, high_risks: list, medium_risks: list) -> List[Any]:
7181
"""Risks to printable string.
7282
7383
Args:
74-
high_risk (list): Lists of high risks.
75-
medium_risk (list): Lists of medium risks.
84+
high_risks (list): Lists of high risks.
85+
medium_risks (list): Lists of medium risks.
7686
7787
Raises:
7888
CensysCLIException: No information/risks found.
7989
8090
Returns:
81-
str: Printable string for CLI.
91+
list: Printable objects for CLI.
8292
"""
83-
len_high_risk = len(high_risk)
84-
len_medium_risk = len(medium_risk)
93+
len_high_risk = len(high_risks)
94+
len_medium_risk = len(medium_risks)
8595

8696
if len_high_risk + len_medium_risk == 0:
8797
raise CensysCLIException
8898

89-
response = ""
99+
response: List[Any] = []
90100
if len_high_risk > 0:
91-
response = (
92-
response
93-
+ "[bold red]:exclamation: High Risks Found:[/bold red] \n"
94-
+ "\n".join([risk.get("string") for risk in high_risk])
101+
response.append(
102+
self.make_risks_into_table(
103+
":exclamation: High Risks Found",
104+
high_risks,
105+
)
95106
)
96107
else:
97-
response = response + "You don't have any High Risks in your network\n"
108+
response.append("You don't have any High Risks in your network\n")
98109
if len_medium_risk > 0:
99-
response = (
100-
response
101-
+ "[bold orange]:grey_exclamation: Medium Risks Found:[/bold orange] \n"
102-
+ "\n".join([risk.get("string") for risk in medium_risk])
110+
response.append(
111+
self.make_risks_into_table(
112+
":grey_exclamation: Medium Risks Found",
113+
medium_risks,
114+
)
103115
)
104116
else:
105-
response = response + "You don't have any Medium Risks in your network\n"
117+
response.append("You don't have any Medium Risks in your network\n")
106118
return response
107119

108-
def view_current_ip_risks(self) -> str:
109-
"""Gets protocol information for the current IP and returns any risks.
110-
111-
Returns:
112-
str: Printable
113-
"""
120+
def view_current_ip_risks(self):
121+
"""Gets protocol information for the current IP and returns any risks."""
114122
current_ip = self.get_current_ip()
115123

116124
try:
125+
console.print(f"Searching for information on {current_ip}...")
117126
results = self.index.view(current_ip)
118127
services = results.get("services", [])
119128
high_risk, medium_risk = self.translate_risk(services)
120-
return self.risks_to_string(high_risk, medium_risk)
129+
for res in self.risks_to_string(high_risk, medium_risk):
130+
console.print(res)
131+
console.print(
132+
f"\nFor more information, please visit: https://search.censys.io/hosts/{current_ip}"
133+
)
121134
except (CensysNotFoundException, CensysCLIException):
122-
return (
135+
console.print(
123136
"[green]:white_check_mark: No Risks were found on your network[/green]"
124137
)
125138

@@ -136,9 +149,7 @@ def cli_hnri(args: argparse.Namespace):
136149

137150
client = CensysHNRI(args.api_id, args.api_secret)
138151

139-
risks = client.view_current_ip_risks()
140-
141-
print(risks)
152+
client.view_current_ip_risks()
142153

143154

144155
def include(parent_parser: argparse._SubParsersAction, parents: dict):

censys/cli/commands/search.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import List
66
from urllib.parse import urlencode
77

8-
from ..utils import INDEXES, V1_INDEXES, V2_INDEXES, console, write_file
8+
from censys.cli.utils import INDEXES, V1_INDEXES, V2_INDEXES, console, write_file
99
from censys.common.exceptions import CensysCLIException
1010
from censys.search import SearchClient
1111

@@ -142,7 +142,7 @@ def cli_search(args: argparse.Namespace):
142142
try:
143143
write_file(results, **write_args)
144144
except ValueError as error: # pragma: no cover
145-
print(f"Error writing log file. Error: {error}")
145+
console.print(f"Error writing log file. Error: {error}")
146146

147147

148148
def include(parent_parser: argparse._SubParsersAction, parents: dict):

censys/cli/commands/view.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import sys
44
import webbrowser
55

6-
from ..utils import V2_INDEXES, valid_datetime_type, write_file
6+
from censys.cli.utils import V2_INDEXES, console, valid_datetime_type, write_file
77
from censys.search import SearchClient
8+
from censys.search.v2.api import CensysSearchAPIv2
89

910

1011
def cli_view(args: argparse.Namespace):
@@ -29,7 +30,7 @@ def cli_view(args: argparse.Namespace):
2930

3031
c = SearchClient(**censys_args)
3132

32-
index = getattr(c.v2, args.index_type)
33+
index: CensysSearchAPIv2 = getattr(c.v2, args.index_type)
3334

3435
view_args = {}
3536
write_args = {
@@ -46,7 +47,7 @@ def cli_view(args: argparse.Namespace):
4647
try:
4748
write_file(document, **write_args)
4849
except ValueError as error: # pragma: no cover
49-
print(f"Error writing log file. Error: {error}")
50+
console.print(f"Error writing log file. Error: {error}")
5051

5152

5253
def include(parent_parser: argparse._SubParsersAction, parents: dict):

0 commit comments

Comments
 (0)