Fix Sonar issue, formatting (#99)

* Fix Sonar issue, formatting

* Remove dev dependencies from requirements.tzt

* Fix unit test failure
This commit is contained in:
Luke Tainton 2023-06-04 12:06:30 +01:00 committed by GitHub
parent dd8411d726
commit 950d1164eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 48 additions and 58 deletions

View File

@ -2,4 +2,4 @@
"""MODULE: Specifies app version.""" """MODULE: Specifies app version."""
VERSION = "1.2" # pragma: no cover VERSION = "1.3" # pragma: no cover

View File

@ -2,19 +2,18 @@
"""MODULE: Provides functions to call various APIs to retrieve IP/prefix information.""" """MODULE: Provides functions to call various APIs to retrieve IP/prefix information."""
from typing import Union
import ipaddress import ipaddress
import requests import requests
def get_ip_information(ipv4_address: ipaddress.IPv4Address) -> dict: def get_ip_information(ipv4_address: ipaddress.IPv4Address) -> dict | None:
"""Retrieves information about a given IPv4 address from IP-API.com.""" """Retrieves information about a given IPv4 address from IP-API.com."""
api_endpoint = f"http://ip-api.com/json/{ipv4_address}" api_endpoint: str = f"http://ip-api.com/json/{ipv4_address}"
try: try:
resp = requests.get(api_endpoint, timeout=10) resp: requests.Response = requests.get(api_endpoint, timeout=10)
resp.raise_for_status() resp.raise_for_status()
ret = resp.json() if resp.json().get("status") == "success" else None ret: dict | None = resp.json() if resp.json().get("status") == "success" else None
except (requests.exceptions.JSONDecodeError, requests.exceptions.HTTPError): except (requests.exceptions.JSONDecodeError, requests.exceptions.HTTPError):
ret = None ret = None
return ret return ret
@ -22,18 +21,18 @@ def get_ip_information(ipv4_address: ipaddress.IPv4Address) -> dict:
def get_autonomous_system_number(as_info: str) -> str: def get_autonomous_system_number(as_info: str) -> str:
"""Parses AS number from provided AS information.""" """Parses AS number from provided AS information."""
as_number = as_info.split(" ")[0] as_number: str = as_info.split(" ")[0]
return as_number return as_number
def get_prefix_information(autonomous_system: int) -> Union[list, None]: def get_prefix_information(autonomous_system: str) -> list | None:
"""Retrieves prefix information about a given autonomous system.""" """Retrieves prefix information about a given autonomous system."""
api_endpoint = f"https://api.hackertarget.com/aslookup/?q={str(autonomous_system)}" api_endpoint: str = f"https://api.hackertarget.com/aslookup/?q={str(autonomous_system)}"
try: try:
resp = requests.get(api_endpoint, timeout=10) resp: requests.Response = requests.get(api_endpoint, timeout=10)
resp.raise_for_status() resp.raise_for_status()
except requests.exceptions.HTTPError: except requests.exceptions.HTTPError:
return None return None
prefixes = resp.text.split("\n") prefixes: list[str] = resp.text.split("\n")
prefixes.pop(0) prefixes.pop(0)
return prefixes return prefixes

View File

@ -5,14 +5,13 @@
import sys import sys
from app.args import parse_args from app.args import parse_args
from app.print_table import print_table, generate_prefix_string
from app.query_normalisation import is_ip_address, resolve_domain_name
from app.ip_info import ( from app.ip_info import (
get_ip_information,
get_autonomous_system_number, get_autonomous_system_number,
get_ip_information,
get_prefix_information, get_prefix_information,
) )
from app.print_table import generate_prefix_string, print_table
from app.query_normalisation import is_ip_address, resolve_domain_name
HEADER = """----------------------------------------------- HEADER = """-----------------------------------------------
| IP Address Information Lookup Tool (iPilot) | | IP Address Information Lookup Tool (iPilot) |
@ -20,16 +19,14 @@ HEADER = """-----------------------------------------------
-----------------------------------------------\n""" -----------------------------------------------\n"""
def main(): def main() -> None:
"""Main function.""" """Main function."""
args = parse_args() args = parse_args()
if not args.noheader: if not args.noheader:
print(HEADER) print(HEADER)
# Set IP to passed IP address, or resolve passed domain name to IPv4 # Set IP to passed IP address, or resolve passed domain name to IPv4
ip_address = ( ip_address = resolve_domain_name(args.query) if not is_ip_address(args.query) else args.query
resolve_domain_name(args.query) if not is_ip_address(args.query) else args.query
)
# If not given an IPv4, and can't resolve to IPv4, then throw error and exit # If not given an IPv4, and can't resolve to IPv4, then throw error and exit
if not ip_address: if not ip_address:
@ -37,22 +34,22 @@ def main():
sys.exit(1) sys.exit(1)
# Get information from API # Get information from API
ip_info = get_ip_information(ip_address) ip_info: dict | None = get_ip_information(ip_address)
if not ip_info: if not ip_info:
print("ERROR: could not retrieve IP information from API.") print("ERROR: could not retrieve IP information from API.")
sys.exit(1) sys.exit(1)
as_number = get_autonomous_system_number(ip_info.get("as")) as_number: str = get_autonomous_system_number(ip_info["as"])
# Assemble list for table generation # Assemble list for table generation
country = ip_info.get("country") country: str = ip_info["country"]
region = ip_info.get("regionName") region: str = ip_info["regionName"]
city = ip_info.get("city") city: str = ip_info["city"]
table_data = [ table_data: list = [
["IP Address", ip_info.get("query")], ["IP Address", ip_info["query"]],
["Organization", ip_info.get("org")], ["Organization", ip_info["org"]],
["Location", f"{country}/{region}/{city}"], ["Location", f"{country}/{region}/{city}"],
["Timezone", ip_info.get("timezone")], ["Timezone", ip_info["timezone"]],
["Internet Service Provider", ip_info.get("isp")], ["Internet Service Provider", ip_info["isp"]],
["Autonomous System", as_number], ["Autonomous System", as_number],
] ]

View File

@ -2,15 +2,14 @@
"""MODULE: Provides functions for preparing, then printing, retrieved data.""" """MODULE: Provides functions for preparing, then printing, retrieved data."""
from typing import Union
from tabulate import tabulate from tabulate import tabulate
def generate_prefix_string(prefixes: list) -> Union[str, None]: def generate_prefix_string(prefixes: list) -> str | None:
"""Generate a string that spilts prefixes into rows of 4.""" """Generate a string that spilts prefixes into rows of 4."""
num_per_row = 4 num_per_row = 4
try: try:
ret = "" ret: str = ""
for i in range(0, len(prefixes), num_per_row): for i in range(0, len(prefixes), num_per_row):
ret += ", ".join(prefixes[i : i + num_per_row]) + "\n" ret += ", ".join(prefixes[i : i + num_per_row]) + "\n"
return ret return ret

View File

@ -3,8 +3,9 @@
"""MODULE: Provides functions that ensure an IP address is """MODULE: Provides functions that ensure an IP address is
available to query the APIs for.""" available to query the APIs for."""
import socket
import ipaddress import ipaddress
import socket
import requests import requests
@ -17,16 +18,17 @@ def is_ip_address(query: str) -> bool:
return False return False
def resolve_domain_name(domain_name: str) -> ipaddress.IPv4Address: def resolve_domain_name(domain_name: str) -> ipaddress.IPv4Address | None:
"""Resolve a domain name via DNS or return None.""" """Resolve a domain name via DNS or return None."""
try: try:
ip_address = socket.gethostbyname(domain_name) result: str = socket.gethostbyname(domain_name)
except socket.gaierror: ip_address: ipaddress.IPv4Address = ipaddress.IPv4Address(result)
ip_address = None return ip_address
return ip_address except (socket.gaierror, ipaddress.AddressValueError):
return None
def get_public_ip() -> ipaddress.IPv4Address: def get_public_ip() -> ipaddress.IPv4Address:
"""Get the user's current public IPv4 address.""" """Get the user's current public IPv4 address."""
ip_address = requests.get("https://api.ipify.org", timeout=10).text ip_address: str = requests.get("https://api.ipify.org", timeout=10).text
return ip_address return ipaddress.IPv4Address(ip_address)

View File

@ -1,15 +1,12 @@
astroid==2.15.5 astroid==2.15.5
attrs==23.1.0 attrs==23.1.0
black==23.3.0
certifi==2023.5.7 certifi==2023.5.7
charset-normalizer==3.1.0 charset-normalizer==3.1.0
click==8.1.3 click==8.1.3
coverage==7.2.7
dill==0.3.6 dill==0.3.6
exceptiongroup==1.1.1 exceptiongroup==1.1.1
idna==3.4 idna==3.4
iniconfig==2.0.0 iniconfig==2.0.0
isort==5.12.0
lazy-object-proxy==1.9.0 lazy-object-proxy==1.9.0
mccabe==0.7.0 mccabe==0.7.0
mypy-extensions==1.0.0 mypy-extensions==1.0.0
@ -18,11 +15,8 @@ pathspec==0.11.1
platformdirs==3.5.1 platformdirs==3.5.1
pluggy==1.0.0 pluggy==1.0.0
py==1.11.0 py==1.11.0
pylint==2.17.4
pyparsing==3.0.9 pyparsing==3.0.9
pytest==7.3.1
requests==2.31.0 requests==2.31.0
requests-mock==1.10.0
six==1.16.0 six==1.16.0
tabulate==0.9.0 tabulate==0.9.0
tomli==2.0.1 tomli==2.0.1

View File

@ -6,7 +6,6 @@ from setuptools import setup
from app._version import VERSION from app._version import VERSION
dependencies = [] dependencies = []
with open("requirements.txt", "r", encoding="ascii") as dep_file: with open("requirements.txt", "r", encoding="ascii") as dep_file:
for dep_line in dep_file.readlines(): for dep_line in dep_file.readlines():

View File

@ -5,15 +5,15 @@
import requests_mock import requests_mock
from app.ip_info import ( # pragma: no cover from app.ip_info import ( # pragma: no cover
get_ip_information,
get_autonomous_system_number, get_autonomous_system_number,
get_ip_information,
get_prefix_information, get_prefix_information,
) )
def test_get_ip_information() -> None: def test_get_ip_information() -> None:
"""TEST: ensure that the IP information API is working correctly.""" """TEST: ensure that the IP information API is working correctly."""
test_query = "1.2.3.4" test_query: str = "1.2.3.4"
ip_info = get_ip_information(test_query) ip_info = get_ip_information(test_query)
assert ip_info.get("status") == "success" and ip_info.get("query") == test_query assert ip_info.get("status") == "success" and ip_info.get("query") == test_query
@ -39,7 +39,7 @@ def test_get_ip_information_bad_response() -> None:
def test_get_autonomous_system_number() -> None: def test_get_autonomous_system_number() -> None:
"""TEST: ensure that AS information is parsed into AS number correctly.""" """TEST: ensure that AS information is parsed into AS number correctly."""
as_info = "AS5089 Virgin Media Limited" as_info = "AS5089 Virgin Media Limited"
as_number = get_autonomous_system_number(as_info) as_number: str = get_autonomous_system_number(as_info)
assert as_number == "AS5089" assert as_number == "AS5089"

View File

@ -7,13 +7,13 @@ from app.print_table import generate_prefix_string # pragma: no cover
def test_generate_prefix_string_small() -> None: def test_generate_prefix_string_small() -> None:
"""TEST: Verifies if a small prefix list results in one line.""" """TEST: Verifies if a small prefix list results in one line."""
test_query = ["abc", "def"] test_query: list[str] = ["abc", "def"]
result = generate_prefix_string(prefixes=test_query) result: str | None = generate_prefix_string(prefixes=test_query)
assert result == "abc, def\n" assert result == "abc, def\n"
def test_generate_prefix_string_large() -> None: def test_generate_prefix_string_large() -> None:
"""TEST: Verifies if a large prefix list results in multiple lines.""" """TEST: Verifies if a large prefix list results in multiple lines."""
test_query = ["abc", "def", "ghi", "jkl", "mno", "pqr"] test_query: list[str] = ["abc", "def", "ghi", "jkl", "mno", "pqr"]
result = generate_prefix_string(prefixes=test_query) result: str | None = generate_prefix_string(prefixes=test_query)
assert result == "abc, def, ghi, jkl\nmno, pqr\n" assert result == "abc, def, ghi, jkl\nmno, pqr\n"

View File

@ -3,9 +3,9 @@
"""MODULE: Provides test cases for app/query_normalisation.py.""" """MODULE: Provides test cases for app/query_normalisation.py."""
from app.query_normalisation import ( # pragma: no cover from app.query_normalisation import ( # pragma: no cover
get_public_ip,
is_ip_address, is_ip_address,
resolve_domain_name, resolve_domain_name,
get_public_ip,
) )
@ -30,8 +30,8 @@ def test_is_ip_address_false_fqdn() -> None:
def test_resolve_domain_name_true() -> None: def test_resolve_domain_name_true() -> None:
"""TEST: Verifies that DNS resolution is working correctly.""" """TEST: Verifies that DNS resolution is working correctly."""
domain_name = "one.one.one.one" domain_name = "one.one.one.one"
expected_results = ["1.1.1.1", "1.0.0.1"] # Could resolve to either IP expected_results: list[str] = ["1.1.1.1", "1.0.0.1"] # Could resolve to either IP
assert resolve_domain_name(domain_name) in expected_results assert str(resolve_domain_name(domain_name)) in expected_results
def test_resolve_domain_name_false() -> None: def test_resolve_domain_name_false() -> None: