Fix Sonar issue, formatting #99
| @@ -2,4 +2,4 @@ | ||||
|  | ||||
| """MODULE: Specifies app version.""" | ||||
|  | ||||
| VERSION = "1.2"  # pragma: no cover | ||||
| VERSION = "1.3"  # pragma: no cover | ||||
|   | ||||
| @@ -2,19 +2,18 @@ | ||||
|  | ||||
| """MODULE: Provides functions to call various APIs to retrieve IP/prefix information.""" | ||||
|  | ||||
| from typing import Union | ||||
|  | ||||
| import ipaddress | ||||
|  | ||||
| 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.""" | ||||
|     api_endpoint = f"http://ip-api.com/json/{ipv4_address}" | ||||
|     api_endpoint: str = f"http://ip-api.com/json/{ipv4_address}" | ||||
|     try: | ||||
|         resp = requests.get(api_endpoint, timeout=10) | ||||
|         resp: requests.Response = requests.get(api_endpoint, timeout=10) | ||||
|         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): | ||||
|         ret = None | ||||
|     return ret | ||||
| @@ -22,18 +21,18 @@ def get_ip_information(ipv4_address: ipaddress.IPv4Address) -> dict: | ||||
|  | ||||
| def get_autonomous_system_number(as_info: str) -> str: | ||||
|     """Parses AS number from provided AS information.""" | ||||
|     as_number = as_info.split(" ")[0] | ||||
|     as_number: str = as_info.split(" ")[0] | ||||
|     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.""" | ||||
|     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: | ||||
|         resp = requests.get(api_endpoint, timeout=10) | ||||
|         resp: requests.Response = requests.get(api_endpoint, timeout=10) | ||||
|         resp.raise_for_status() | ||||
|     except requests.exceptions.HTTPError: | ||||
|         return None | ||||
|     prefixes = resp.text.split("\n") | ||||
|     prefixes: list[str] = resp.text.split("\n") | ||||
|     prefixes.pop(0) | ||||
|     return prefixes | ||||
|   | ||||
							
								
								
									
										33
									
								
								app/main.py
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								app/main.py
									
									
									
									
									
								
							| @@ -5,14 +5,13 @@ | ||||
| import sys | ||||
|  | ||||
| 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 ( | ||||
|     get_ip_information, | ||||
|     get_autonomous_system_number, | ||||
|     get_ip_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 = """----------------------------------------------- | ||||
| | IP Address Information Lookup Tool (iPilot) | | ||||
| @@ -20,16 +19,14 @@ HEADER = """----------------------------------------------- | ||||
| -----------------------------------------------\n""" | ||||
|  | ||||
|  | ||||
| def main(): | ||||
| def main() -> None: | ||||
|     """Main function.""" | ||||
|     args = parse_args() | ||||
|     if not args.noheader: | ||||
|         print(HEADER) | ||||
|  | ||||
|     # Set IP to passed IP address, or resolve passed domain name to IPv4 | ||||
|     ip_address = ( | ||||
|         resolve_domain_name(args.query) if not is_ip_address(args.query) else args.query | ||||
|     ) | ||||
|     ip_address = 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 ip_address: | ||||
| @@ -37,22 +34,22 @@ def main(): | ||||
|         sys.exit(1) | ||||
|  | ||||
|     # Get information from API | ||||
|     ip_info = get_ip_information(ip_address) | ||||
|     ip_info: dict | None = get_ip_information(ip_address) | ||||
|     if not ip_info: | ||||
|         print("ERROR: could not retrieve IP information from API.") | ||||
|         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 | ||||
|     country = ip_info.get("country") | ||||
|     region = ip_info.get("regionName") | ||||
|     city = ip_info.get("city") | ||||
|     table_data = [ | ||||
|         ["IP Address", ip_info.get("query")], | ||||
|         ["Organization", ip_info.get("org")], | ||||
|     country: str = ip_info["country"] | ||||
|     region: str = ip_info["regionName"] | ||||
|     city: str = ip_info["city"] | ||||
|     table_data: list = [ | ||||
|         ["IP Address", ip_info["query"]], | ||||
|         ["Organization", ip_info["org"]], | ||||
|         ["Location", f"{country}/{region}/{city}"], | ||||
|         ["Timezone", ip_info.get("timezone")], | ||||
|         ["Internet Service Provider", ip_info.get("isp")], | ||||
|         ["Timezone", ip_info["timezone"]], | ||||
|         ["Internet Service Provider", ip_info["isp"]], | ||||
|         ["Autonomous System", as_number], | ||||
|     ] | ||||
|  | ||||
|   | ||||
| @@ -2,15 +2,14 @@ | ||||
|  | ||||
| """MODULE: Provides functions for preparing, then printing, retrieved data.""" | ||||
|  | ||||
| from typing import Union | ||||
| 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.""" | ||||
|     num_per_row = 4 | ||||
|     try: | ||||
|         ret = "" | ||||
|         ret: str = "" | ||||
|         for i in range(0, len(prefixes), num_per_row): | ||||
|             ret += ", ".join(prefixes[i : i + num_per_row]) + "\n" | ||||
|         return ret | ||||
|   | ||||
| @@ -3,8 +3,9 @@ | ||||
| """MODULE: Provides functions that ensure an IP address is | ||||
| available to query the APIs for.""" | ||||
|  | ||||
| import socket | ||||
| import ipaddress | ||||
| import socket | ||||
|  | ||||
| import requests | ||||
|  | ||||
|  | ||||
| @@ -17,16 +18,17 @@ def is_ip_address(query: str) -> bool: | ||||
|         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.""" | ||||
|     try: | ||||
|         ip_address = socket.gethostbyname(domain_name) | ||||
|     except socket.gaierror: | ||||
|         ip_address = None | ||||
|     return ip_address | ||||
|         result: str = socket.gethostbyname(domain_name) | ||||
|         ip_address: ipaddress.IPv4Address = ipaddress.IPv4Address(result) | ||||
|         return ip_address | ||||
|     except (socket.gaierror, ipaddress.AddressValueError): | ||||
|         return None | ||||
|  | ||||
|  | ||||
| def get_public_ip() -> ipaddress.IPv4Address: | ||||
|     """Get the user's current public IPv4 address.""" | ||||
|     ip_address = requests.get("https://api.ipify.org", timeout=10).text | ||||
|     return ip_address | ||||
|     ip_address: str = requests.get("https://api.ipify.org", timeout=10).text | ||||
|     return ipaddress.IPv4Address(ip_address) | ||||
|   | ||||
| @@ -1,15 +1,12 @@ | ||||
| astroid==2.15.5 | ||||
| attrs==23.1.0 | ||||
| black==23.3.0 | ||||
| certifi==2023.5.7 | ||||
| charset-normalizer==3.1.0 | ||||
| click==8.1.3 | ||||
| coverage==7.2.7 | ||||
| dill==0.3.6 | ||||
| exceptiongroup==1.1.1 | ||||
| idna==3.4 | ||||
| iniconfig==2.0.0 | ||||
| isort==5.12.0 | ||||
| lazy-object-proxy==1.9.0 | ||||
| mccabe==0.7.0 | ||||
| mypy-extensions==1.0.0 | ||||
| @@ -18,11 +15,8 @@ pathspec==0.11.1 | ||||
| platformdirs==3.5.1 | ||||
| pluggy==1.0.0 | ||||
| py==1.11.0 | ||||
| pylint==2.17.4 | ||||
| pyparsing==3.0.9 | ||||
| pytest==7.3.1 | ||||
| requests==2.31.0 | ||||
| requests-mock==1.10.0 | ||||
| six==1.16.0 | ||||
| tabulate==0.9.0 | ||||
| tomli==2.0.1 | ||||
|   | ||||
							
								
								
									
										1
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
									
									
									
									
								
							| @@ -6,7 +6,6 @@ from setuptools import setup | ||||
|  | ||||
| from app._version import VERSION | ||||
|  | ||||
|  | ||||
| dependencies = [] | ||||
| with open("requirements.txt", "r", encoding="ascii") as dep_file: | ||||
|     for dep_line in dep_file.readlines(): | ||||
|   | ||||
| @@ -5,15 +5,15 @@ | ||||
| import requests_mock | ||||
|  | ||||
| from app.ip_info import (  # pragma: no cover | ||||
|     get_ip_information, | ||||
|     get_autonomous_system_number, | ||||
|     get_ip_information, | ||||
|     get_prefix_information, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def test_get_ip_information() -> None: | ||||
|     """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) | ||||
|     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: | ||||
|     """TEST: ensure that AS information is parsed into AS number correctly.""" | ||||
|     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" | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -7,13 +7,13 @@ from app.print_table import generate_prefix_string  # pragma: no cover | ||||
|  | ||||
| def test_generate_prefix_string_small() -> None: | ||||
|     """TEST: Verifies if a small prefix list results in one line.""" | ||||
|     test_query = ["abc", "def"] | ||||
|     result = generate_prefix_string(prefixes=test_query) | ||||
|     test_query: list[str] = ["abc", "def"] | ||||
|     result: str | None = generate_prefix_string(prefixes=test_query) | ||||
|     assert result == "abc, def\n" | ||||
|  | ||||
|  | ||||
| def test_generate_prefix_string_large() -> None: | ||||
|     """TEST: Verifies if a large prefix list results in multiple lines.""" | ||||
|     test_query = ["abc", "def", "ghi", "jkl", "mno", "pqr"] | ||||
|     result = generate_prefix_string(prefixes=test_query) | ||||
|     test_query: list[str] = ["abc", "def", "ghi", "jkl", "mno", "pqr"] | ||||
|     result: str | None = generate_prefix_string(prefixes=test_query) | ||||
|     assert result == "abc, def, ghi, jkl\nmno, pqr\n" | ||||
|   | ||||
| @@ -3,9 +3,9 @@ | ||||
| """MODULE: Provides test cases for app/query_normalisation.py.""" | ||||
|  | ||||
| from app.query_normalisation import (  # pragma: no cover | ||||
|     get_public_ip, | ||||
|     is_ip_address, | ||||
|     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: | ||||
|     """TEST: Verifies that DNS resolution is working correctly.""" | ||||
|     domain_name = "one.one.one.one" | ||||
|     expected_results = ["1.1.1.1", "1.0.0.1"]  # Could resolve to either IP | ||||
|     assert resolve_domain_name(domain_name) in expected_results | ||||
|     expected_results: list[str] = ["1.1.1.1", "1.0.0.1"]  # Could resolve to either IP | ||||
|     assert str(resolve_domain_name(domain_name)) in expected_results | ||||
|  | ||||
|  | ||||
| def test_resolve_domain_name_false() -> None: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user