From c110533a12585baf062423e9acd09772159e9f74 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 13:34:27 +1000 Subject: [PATCH 01/11] wip(feat): logging configuration (not yet implemented) --- Config/logger.ini | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Config/logger.ini diff --git a/Config/logger.ini b/Config/logger.ini new file mode 100644 index 0000000..5207d51 --- /dev/null +++ b/Config/logger.ini @@ -0,0 +1,22 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=DEBUG +handlers=consoleHandler + +[handler_consoleHandler] +class=StreamHandler +level=DEBUG +formatter=simpleFormatter +args=(sys.stdout,) + +[formatter_simpleFormatter] +format=%(asctime)s %(levelname)s %(message)s 🔍 (debug) 💡 (info) âš ī¸ (warning) 🆘 (critical) +datefmt=%Y-%m-%d %H:%M:%S From 2c5f39aee29303f7fb5c844388a79a8063ecd7a4 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 13:37:44 +1000 Subject: [PATCH 02/11] feat: configure logging function for modular logging --- Config/config.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Config/config.py b/Config/config.py index c6f6f49..8728cb9 100644 --- a/Config/config.py +++ b/Config/config.py @@ -10,4 +10,13 @@ # utils.error_message(er) # # Get the Virus Total API key from the config file -# VT_APIKey = config['APIKeys']['VT_APIKey'] \ No newline at end of file +# VT_APIKey = config['APIKeys']['VT_APIKey'] +import logging.config + +def configure_logging(file='config.ini'): + """Set up default logger for logging messages. + + :param `file`: File to set configuration from. + """ + + logging.config.fileConfig(file) From 82c64cc791463a42d365ca7354f68c67cc241fba Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 13:39:30 +1000 Subject: [PATCH 03/11] lint: linting across all files. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reformatted /home/josh/projects/Cyber-Breach-Detector/Config/config.py reformatted /home/josh/projects/Cyber-Breach-Detector/Common/breach_checker.py reformatted /home/josh/projects/Cyber-Breach-Detector/IP/ip_reputation_checker.py reformatted /home/josh/projects/Cyber-Breach-Detector/main.py reformatted /home/josh/projects/Cyber-Breach-Detector/Common/utils.py reformatted /home/josh/projects/Cyber-Breach-Detector/Email/email_reputation_checker.py reformatted /home/josh/projects/Cyber-Breach-Detector/Username/username_reputation_checker.py All done! ✨ 🍰 ✨ 7 files reformatted, 4 files left unchanged. --- Config/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Config/config.py b/Config/config.py index 8728cb9..6fac567 100644 --- a/Config/config.py +++ b/Config/config.py @@ -13,7 +13,8 @@ # VT_APIKey = config['APIKeys']['VT_APIKey'] import logging.config -def configure_logging(file='config.ini'): + +def configure_logging(file="config.ini"): """Set up default logger for logging messages. :param `file`: File to set configuration from. From 32ef1f6266351e2b0dfd5acc52936824f19d8319 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 13:45:24 +1000 Subject: [PATCH 04/11] lint: lint across all files note: previous commit didn't go thru --- Config/config.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Config/config.py b/Config/config.py index 6fac567..961670d 100644 --- a/Config/config.py +++ b/Config/config.py @@ -1,17 +1,6 @@ -# FILE CURRENTLY NOT USED - -# import configparser -# from Common import utils - -# try: -# config = configparser.ConfigParser() -# config.read('config.ini') -# except Exception as er: -# utils.error_message(er) - -# # Get the Virus Total API key from the config file -# VT_APIKey = config['APIKeys']['VT_APIKey'] +import configparser import logging.config +from Common import utils def configure_logging(file="config.ini"): @@ -20,4 +9,11 @@ def configure_logging(file="config.ini"): :param `file`: File to set configuration from. """ - logging.config.fileConfig(file) + try: + config = configparser.ConfigParser() + config.read('config.ini') + except Exception as er: + utils.error_message(er) + finally: + logging.config.fileConfig(file) + return From b0d326c4d42e70b0ec64cea707530d54cc08cb2b Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 18:19:59 +1000 Subject: [PATCH 05/11] feat: logging --- Config/check_config_validity.py | 23 +++++++++++++ Config/config.py | 57 ++++++++++++++++++++++++++------- Config/formatter.py | 24 ++++++++++++++ Config/logger.ini | 43 ++++++++++++++++--------- main.py | 42 +++++++++++++++++------- requirements.txt | 3 +- 6 files changed, 152 insertions(+), 40 deletions(-) create mode 100644 Config/check_config_validity.py create mode 100644 Config/formatter.py diff --git a/Config/check_config_validity.py b/Config/check_config_validity.py new file mode 100644 index 0000000..957fa1a --- /dev/null +++ b/Config/check_config_validity.py @@ -0,0 +1,23 @@ +import configparser + +config = configparser.ConfigParser() +config.read('Config/logger.ini') + +# check if the required sections are present +if 'loggers' not in config.sections() or 'handlers' not in config.sections(): + raise ValueError('Missing required section in config.ini') + +if 'keys' not in config['loggers'] or 'suspicious' not in config['loggers']: + raise ValueError('Missing required key in loggers section in config.ini') + +# check if the required keys are present in the 'handlers' section +if 'keys' not in config['handlers'] or 'console' not in config['handlers']['keys']: + raise ValueError('Missing required key in handlers section in config.ini') + +# check if the values of the keys are in the expected format +if not isinstance(config.getint('handlers', 'console.level'), int): + raise ValueError('Invalid value for console.level in config.ini') + +if not isinstance(config.getint('loggers', 'keys.suspicious.level'), int): + raise ValueError('Invalid value for keys.suspicious.level in config.ini') + diff --git a/Config/config.py b/Config/config.py index 961670d..967cb0b 100644 --- a/Config/config.py +++ b/Config/config.py @@ -1,19 +1,52 @@ -import configparser +import logging import logging.config -from Common import utils +import configparser +import os +import sys -def configure_logging(file="config.ini"): - """Set up default logger for logging messages. +def get_config(config_file_path): + """ + Parse configuration from file and environment variables. - :param `file`: File to set configuration from. + :param str config_file_path: path to the configuration file + :return: configuration values as a dictionary + :rtype: dict """ + config = configparser.ConfigParser() + + # read from file + config.read(config_file_path) + # read from environment variables + for key in config['ENVIRONMENT']: + env_var = os.environ.get(key) + if env_var is not None: + config['ENVIRONMENT'][key] = env_var + + return config + + +def get_logging_config(): + """ + Get logging configuration from configuration file. + + :return: logging configuration as a dictionary + :rtype: dict + """ try: - config = configparser.ConfigParser() - config.read('config.ini') - except Exception as er: - utils.error_message(er) - finally: - logging.config.fileConfig(file) - return + open('Config/logger.ini') + except Exception: + print("File location invalid ") + sys.exit(1) + + config = get_config('Config/logger.ini') + return config + + +def configure_logging(): + """ + Configure logging for the application. + """ + logging_config = get_logging_config() + return logging_config diff --git a/Config/formatter.py b/Config/formatter.py new file mode 100644 index 0000000..79ccd28 --- /dev/null +++ b/Config/formatter.py @@ -0,0 +1,24 @@ +import logging + + +class Formatter(logging.Formatter): + def __init__(self, fmt=None, datefmt=None, levels=None): + super().__init__(fmt, datefmt) + self.levels = levels or {} + + def format(self, record): + record.levelprefix = self.levels.get(record.levelno, '') + emoji = '' + if record.levelno == logging.CRITICAL: + emoji = 'đŸ’Ŗ' + elif record.levelno == logging.ERROR: + emoji = 'đŸ”Ĩ' + elif record.levelno == logging.WARNING: + emoji = 'âš ī¸' + elif record.levelno == logging.INFO: + emoji = 'â„šī¸' + elif record.levelno == logging.DEBUG: + emoji = '🔍' + record.emoji = emoji + return super().format(record) + diff --git a/Config/logger.ini b/Config/logger.ini index 5207d51..b31a355 100644 --- a/Config/logger.ini +++ b/Config/logger.ini @@ -1,22 +1,35 @@ -[loggers] -keys=root +[ENVIRONMENT] +production = False + +[logging] +level = INFO +handler = console,file +formatter = default +output = Logs/traceback.log +debug_emoji = 🐛 +info_emoji = 💡 +warning_emoji = âš ī¸ +error_emoji = 🚨 +critical_emoji = đŸ’Ŗ [handlers] -keys=consoleHandler +keys = console,file [formatters] -keys=simpleFormatter +keys = default -[logger_root] -level=DEBUG -handlers=consoleHandler +[handler_console] +class = logging.StreamHandler +level = INFO +formatter = default +args = (sys.stdout,) -[handler_consoleHandler] -class=StreamHandler -level=DEBUG -formatter=simpleFormatter -args=(sys.stdout,) +[handler_file] +class = logging.FileHandler +level = INFO +formatter = default +args = ('Logs/traceback.log', 'w') -[formatter_simpleFormatter] -format=%(asctime)s %(levelname)s %(message)s 🔍 (debug) 💡 (info) âš ī¸ (warning) 🆘 (critical) -datefmt=%Y-%m-%d %H:%M:%S +[formatter_default] +format = %(asctime)s - %(name)s - %(levelname)s - %(message)s +datefmt = %Y-%m-%d %H:%M:%S diff --git a/main.py b/main.py index 993a3ef..4eac016 100644 --- a/main.py +++ b/main.py @@ -1,14 +1,31 @@ - -# import requests -import json import argparse +import json +import logging +import logging.config +import sys + +import requests from termcolor import colored + from Common import utils from Common.utils import Validator -from IP.ip_reputation_checker import IPReputationChecker +from Config.config import configure_logging from Email.email_reputation_checker import EmailBreachChecker +from IP.ip_reputation_checker import IPReputationChecker from Username.username_reputation_checker import usernameBreachChecker +import logging +import time + +from selenium import webdriver +import requests + +from Config.config import configure_logging + + + +configure_logging() + def accept_user_input(): # Instantiate the Validator class validator = Validator() @@ -32,7 +49,7 @@ def accept_user_input(): if validator.is_valid_email(args.email) == True: # print(args.email) # Call the email module to check if the email is in a breach - print("Email Validation : \033[92m{}\033[0m".format("Success")) + logging.info("Email Validation : \033[92m{}\033[0m".format("Success")) # Instantiate IPReputationChecker class breach_checker = EmailBreachChecker(args.ip) breach_checker.periodicBreachDownloader() @@ -44,24 +61,24 @@ def accept_user_input(): elif args.ip: # if valid IP, print and return IP if validator.is_valid_ip(args.ip) == True: - print("IP Validation : \033[92m{}\033[0m".format("Success")) + logging.info("IP Validation : \033[92m{}\033[0m".format("Success")) # Instantiate IPReputationChecker class ip_checker = IPReputationChecker(args.ip) # Invoke VT IP Checker module vtip_results = ip_checker.checkIPReputationVT(args.ip) - print("Virus Total results: ", vtip_results) + logging.info("Virus Total results: ", vtip_results) # Invoke OTX IP Checker module # otx_results = ... # print("OTX results:",otx_results) else: - utils.error_message("Invalid IP address") + logging.error("Invalid IP address") elif args.username: if validator.is_valid_username(args.username) == True: # print(args.username) # Call the username module to check if the username is in a breach - print(colored("Username Validation:","grey"),colored("Success","green")) + logging.info(colored("Username Validation:","grey"),colored("Success","green")) # Instantiate IPReputationChecker class breach_checker = usernameBreachChecker(args.ip) @@ -70,9 +87,10 @@ def accept_user_input(): # print("username breach checker module results:", breach_results) # return args.username else: - utils.exit_message("Invalid username") + logging.critical("Invalid username") + sys.exit() else: - utils.error_message("Invalid input received.") + logging.warning("Invalid input received.") if __name__ == '__main__': - accept_user_input() \ No newline at end of file + accept_user_input() diff --git a/requirements.txt b/requirements.txt index f10667a..c1f3cc9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +selenium certifi==2022.12.7 charset-normalizer==3.1.0 distlib==0.3.6 @@ -9,4 +10,4 @@ six==1.16.0 termcolor==2.2.0 six==1.16.0 urllib3==1.26.15 -virtualenv==20.21.0 \ No newline at end of file +virtualenv==20.21.0 From 461906cbc8e76e222def4ece0752a4f517121386 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 18:21:17 +1000 Subject: [PATCH 06/11] bugfix: logfile generated --- Config/logger.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config/logger.ini b/Config/logger.ini index b31a355..8c44020 100644 --- a/Config/logger.ini +++ b/Config/logger.ini @@ -28,7 +28,7 @@ args = (sys.stdout,) class = logging.FileHandler level = INFO formatter = default -args = ('Logs/traceback.log', 'w') +args = ('Logs/traceback.log', 'a') [formatter_default] format = %(asctime)s - %(name)s - %(levelname)s - %(message)s From d0065e6f7507d9ceeb0cec1b410be2ce1c27de52 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 20:16:42 +1000 Subject: [PATCH 07/11] feat: replaced print calls with logs --- Common/breach_checker.py | 9 +++++-- Common/utils.py | 13 ++++++--- Config/logger.ini | 2 +- Email/email_reputation_checker.py | 34 +++++++++++++++--------- IP/ip_reputation_checker.py | 12 ++++++--- Username/username_reputation_checker.py | 35 ++++++++++++++----------- main.py | 15 ++++++----- requirements.txt | 2 +- 8 files changed, 75 insertions(+), 47 deletions(-) diff --git a/Common/breach_checker.py b/Common/breach_checker.py index 0eb8892..c9b2358 100644 --- a/Common/breach_checker.py +++ b/Common/breach_checker.py @@ -2,14 +2,19 @@ from . import utils import re import ipaddress +from Config.config import configure_logging +import logging +configure_logging() + # import argparse # import asyncio + class BreachChecker: def is_valid_email(self, email): # Verify email format # TODO: Add a list of email domains accepted..may be if re.match(r"[^@]+@[^@]+\.[^@]+", email): - print("Input is a valid email address.") + logging.debug("Input is a valid email address.") return True else: # print("Invalid email address.") @@ -20,4 +25,4 @@ def is_valid_ip(self, ip): ipaddress.ip_address(ip) return True except ValueError as err: - return False \ No newline at end of file + return False diff --git a/Common/utils.py b/Common/utils.py index 5044086..c27a75b 100644 --- a/Common/utils.py +++ b/Common/utils.py @@ -11,14 +11,19 @@ import sys import configparser from . import utils +from Config.config import configure_logging +import logging + + +configure_logging() def error_message(errormsg): - print(colored("Error: "+errormsg, 'red')) + logging.error(colored("Error: "+errormsg, 'red')) # TODO: Extend error module to log to error log file # print(errormsg) def exit_message(exitmsg): - print("\033[91m{}\033[0m".format("Fatal Error: "+exitmsg)) + logging.critical("\033[91m{}\033[0m".format("Fatal Error: "+exitmsg)) exit() class Validator: @@ -42,7 +47,7 @@ def is_valid_username(self, username): # Verify username format # TODO: Add a list of username domains accepted..may be if re.match(r"^[a-zA-Z0-9\-\_\!\@\#\$\%\^\&\*\(\)]+", username): - print("Input is a valid username") + logging.debug("Input is a valid username") return True else: # print("Invalid username ") @@ -62,7 +67,7 @@ def check_VTAPIkey(self, VT_APIKey): if 'error' not in data: # Print pretty json response # print(json.dumps(data, indent=4, sort_keys=True)) - print("Virus Total Key Validation: \033[92m{}\033[0m".format("Success")) + logging.info("Virus Total Key Validation: \033[92m{}\033[0m".format("Success")) return True else: error_message(json.dumps(data['error'], indent=4, sort_keys=True)) diff --git a/Config/logger.ini b/Config/logger.ini index 8c44020..6d0a3cc 100644 --- a/Config/logger.ini +++ b/Config/logger.ini @@ -18,7 +18,7 @@ keys = console,file [formatters] keys = default -[handler_console] +[handler_consol] class = logging.StreamHandler level = INFO formatter = default diff --git a/Email/email_reputation_checker.py b/Email/email_reputation_checker.py index 1aaf63a..04a3d86 100644 --- a/Email/email_reputation_checker.py +++ b/Email/email_reputation_checker.py @@ -1,9 +1,17 @@ +from logging.config import fileConfig +from logging import Logger, getLogger import requests import json from termcolor import colored from Common import utils as utils from Common.utils import KeyFetcher from Common.breach_checker import BreachChecker +from Config.config import configure_logging + +import logging + +configure_logging() + class EmailBreachChecker: def __init__(self, email): @@ -23,7 +31,7 @@ def periodicBreachDownloader(self): } try: response = requests.get(url, headers=headers, data=payload).text - print("Downloading full breach data from HIBP") + logging.info("Downloading full breach data from HIBP") breaches = json.loads(response) with open('all_breaches.json', 'w') as f: json.dump(breaches, f) @@ -46,12 +54,12 @@ def checkEmailBreach(self, email): except Exception as e: utils.error_message(e.text) if response.status_code == 404: - print(colored("No breaches found for this email","green")) + logger.debug("{}".format(colored("No breaches found for this email","green"))) exit() data = json.loads(response.text) # Validating the response during execution instead of wasting a call for validation if 'statusCode' not in data: - print("Query successful") + logging.info("Query successful") for breach_name in data: # print("Breach name: ", breach_name['Name']) with open('all_breaches.json', 'r') as f: @@ -61,25 +69,25 @@ def checkEmailBreach(self, email): # print(breaches) for breach in breaches: if 'Name' in breach and breach['Name'] == search_name: - print("\n\n" + colored("Account name:", "blue"), colored(email, "red")) + logging.info("\n\n{}{}".format(colored("Account name:", "blue"), colored(email, "red"))) if 'Name' in breach: - print(colored("Breach name:", "blue"), colored(breach_name['Name'], "red")) + logging.info("{} {}".format(colored("Breach name:", "blue"), colored(breach_name['Name'], "red"))) if 'Description' in breach: - print(colored("Breach description:", "blue"), colored(breach['Description'], "red")) + logging.info("{} {}".format(colored("Breach description:", "blue"), colored(breach['Description'], "red"))) if 'BreachDate' in breach: - print(colored("Breach date:", "blue"), colored(breach['BreachDate'], "white")) + logging.info("{} {}".format(colored("Breach date:", "blue"), colored(breach['BreachDate'], "white"))) if 'DataClasses' in breach: - print(colored("Data classes that were part of this breach:", "blue"), colored(breach['DataClasses'], "red")) + logging.info("{} {}".format(colored("Data classes that were part of this breach:", "blue"), colored(breach['DataClasses'], "red"))) if 'IsSensitive' in breach and breach['IsSensitive'] == True: - print(colored("Breach is sensitive:", "blue"), colored(breach['IsSensitive'], "yellow")) + logging.info("{} {}".format(colored("Breach is sensitive:", "blue"), colored(breach['IsSensitive'], "yellow"))) if 'IsSensitive' in breach and breach['IsSensitive'] == False: - print(colored("Breach is sensitive:", "blue"), colored(breach['IsSensitive'], "green")) + logging.info("{} {}".format(colored("Breach is sensitive:", "blue"), colored(breach['IsSensitive'], "green"))) if 'IsVerified' in breach and breach['IsVerified'] == True: - print(colored("Breach is verified:", "blue"), colored(breach['IsVerified'], "green")) + logging.info("{} {}".format(colored("Breach is verified:", "blue"), colored(breach['IsVerified'], "green"))) if 'IsVerified' in breach and breach['IsVerified'] == False: - print(colored("Breach is verified:", "blue"), colored(breach['IsVerified'], "red")) + logging.info("{} {}".format(colored("Breach is verified:", "blue"), colored(breach['IsVerified'], "red"))) # print(colored("Number of accounts compromised:", "blue"), colored(breach['PwnCount'], "white")) - print(colored("Number of accounts compromised:", "blue"), colored('{:,}'.format(breach['PwnCount']), "white")) + logging.info("{} {}".format(colored("Number of accounts compromised:", "blue"), colored('{:,}'.format(breach['PwnCount']), "white"))) else: pass else: diff --git a/IP/ip_reputation_checker.py b/IP/ip_reputation_checker.py index 4d8980a..0041259 100644 --- a/IP/ip_reputation_checker.py +++ b/IP/ip_reputation_checker.py @@ -1,8 +1,12 @@ +import logging from Common import utils as utils from Common.utils import KeyFetcher from Common.utils import Validator import requests import json +from Config.config import configure_logging + +configure_logging() # IPReputationChecker class inherits methods from BreachChecker super class class IPReputationChecker: @@ -16,7 +20,7 @@ def checkIPReputationVT(self, ip_address): validator = Validator() validator.check_VTAPIkey(vtkey) - print("Checking IP reputation from Virus Total for ip:", ip_address) + logging.info("Checking IP reputation from Virus Total for ip:", ip_address) # Check IP reputation from Virus Total url = "https://www.virustotal.com/api/v3/ip_addresses/"+ip_address payload={} @@ -35,15 +39,15 @@ def checkIPReputationTalos(self, ip_list): # Check IP reputation from Cisco Talos # https://talosintelligence.com/reputation_center/lookup?search= # User requests to check IP reputation from Cisco Talos - print("Checking IP reputation from Cisco Talos") + logging.info("Checking IP reputation from Cisco Talos") for ip in ip_list: url = "https://talosintelligence.com/reputation_center/lookup?search="+ip response = requests.request("GET", url).text if "This IP address has been observed to be associated with malicious activity." in response: - print("IP: "+ip+" is malicious") + logging.info("IP: "+ip+" is malicious") self.bad_ip_list.append(ip) else: - print("IP: "+ip+" is not malicious") + logging.info("IP: "+ip+" is not malicious") # # ================== # def queryIP(apikey, ip_quad, bad_ip_list): diff --git a/Username/username_reputation_checker.py b/Username/username_reputation_checker.py index 2fbb72a..e11b6d2 100644 --- a/Username/username_reputation_checker.py +++ b/Username/username_reputation_checker.py @@ -1,10 +1,15 @@ -import requests import json +import logging + +import requests from termcolor import colored + from Common import utils as utils -from Common.utils import KeyFetcher from Common.breach_checker import BreachChecker +from Common.utils import KeyFetcher +from Config.config import configure_logging +configure_logging() # usernameReputationChecker class inherits methods from BreachChecker super class class usernameBreachChecker: @@ -25,7 +30,7 @@ def periodicBreachDownloader(self): } try: response = requests.get(url, headers=headers, data=payload).text - print("Downloading full breach data from HIBP") + logging.info("Downloading full breach data from HIBP") breaches = json.loads(response) with open('all_breaches.json', 'w') as f: json.dump(breaches, f) @@ -48,7 +53,7 @@ def checkUsernameBreach(self, username): except Exception as e: utils.error_message(e.text) if response.status_code == 404: - print(colored("No breaches found for this username","green")) + logging.info(colored("No breaches found for this username","green")) exit() data = json.loads(response.text) # Validating the response during execution instead of wasting a call for validation @@ -63,25 +68,25 @@ def checkUsernameBreach(self, username): # print(breaches) for breach in breaches: if 'Name' in breach and breach['Name'] == search_name: - print("\n\n" + colored("Account name:", "blue"), colored(username, "red")) + logging.info("\n\n" + colored("Account name:", "blue"), colored(username, "red")) if 'Name' in breach: - print(colored("Breach name:", "blue"), colored(breach_name['Name'], "red")) + logging.info(colored("Breach name:", "blue"), colored(breach_name['Name'], "red")) if 'Description' in breach: - print(colored("Breach description:", "blue"), colored(breach['Description'], "red")) + logging.info(colored("Breach description:", "blue"), colored(breach['Description'], "red")) if 'BreachDate' in breach: - print(colored("Breach date:", "blue"), colored(breach['BreachDate'], "white")) + logging.info(colored("Breach date:", "blue"), colored(breach['BreachDate'], "white")) if 'DataClasses' in breach: - print(colored("Data classes that were part of this breach:", "blue"), colored(breach['DataClasses'], "red")) + logging.info(colored("Data classes that were part of this breach:", "blue"), colored(breach['DataClasses'], "red")) if 'IsSensitive' in breach and breach['IsSensitive'] == True: - print(colored("Breach is sensitive:", "blue"), colored(breach['IsSensitive'], "yellow")) + logging.info(colored("Breach is sensitive:", "blue"), colored(breach['IsSensitive'], "yellow")) if 'IsSensitive' in breach and breach['IsSensitive'] == False: - print(colored("Breach is sensitive:", "blue"), colored(breach['IsSensitive'], "green")) + logging.info(colored("Breach is sensitive:", "blue"), colored(breach['IsSensitive'], "green")) if 'IsVerified' in breach and breach['IsVerified'] == True: - print(colored("Breach is verified:", "blue"), colored(breach['IsVerified'], "green")) + logging.info(colored("Breach is verified:", "blue"), colored(breach['IsVerified'], "green")) if 'IsVerified' in breach and breach['IsVerified'] == False: - print(colored("Breach is verified:", "blue"), colored(breach['IsVerified'], "red")) - print(colored("Number of accounts compromised:", "blue"), colored(breach['PwnCount'], "white")) + logging.info(colored("Breach is verified:", "blue"), colored(breach['IsVerified'], "red")) + logging.info(colored("Number of accounts compromised:", "blue"), colored(breach['PwnCount'], "white")) else: pass else: - utils.error_message(data['message']) \ No newline at end of file + utils.error_message(data['message']) diff --git a/main.py b/main.py index 4eac016..e4eabed 100644 --- a/main.py +++ b/main.py @@ -1,26 +1,27 @@ import argparse import json import logging +import logging import logging.config import sys +import time +from colorama import Fore, init +import requests import requests from termcolor import colored from Common import utils from Common.utils import Validator from Config.config import configure_logging +from Config.config import configure_logging from Email.email_reputation_checker import EmailBreachChecker from IP.ip_reputation_checker import IPReputationChecker from Username.username_reputation_checker import usernameBreachChecker +from Config.config import configure_logging -import logging -import time - -from selenium import webdriver -import requests -from Config.config import configure_logging +init() @@ -78,7 +79,7 @@ def accept_user_input(): if validator.is_valid_username(args.username) == True: # print(args.username) # Call the username module to check if the username is in a breach - logging.info(colored("Username Validation:","grey"),colored("Success","green")) + logging.info("{} {}".format(colored("Username Validation:","grey"), colored("Success","green"))) # Instantiate IPReputationChecker class breach_checker = usernameBreachChecker(args.ip) diff --git a/requirements.txt b/requirements.txt index c1f3cc9..f87ed6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -selenium +colorama certifi==2022.12.7 charset-normalizer==3.1.0 distlib==0.3.6 From ed373e898a0e6239eabc2bef27185d3ad75fff27 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 20:37:31 +1000 Subject: [PATCH 08/11] tests: added unit tests for logging --- Config/config.py | 1 + Config/logger.ini | 2 +- Tests/Config/test_config.py | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Tests/Config/test_config.py diff --git a/Config/config.py b/Config/config.py index 967cb0b..30dc98d 100644 --- a/Config/config.py +++ b/Config/config.py @@ -17,6 +17,7 @@ def get_config(config_file_path): # read from file config.read(config_file_path) + config.__dict__ # read from environment variables for key in config['ENVIRONMENT']: diff --git a/Config/logger.ini b/Config/logger.ini index 6d0a3cc..8c44020 100644 --- a/Config/logger.ini +++ b/Config/logger.ini @@ -18,7 +18,7 @@ keys = console,file [formatters] keys = default -[handler_consol] +[handler_console] class = logging.StreamHandler level = INFO formatter = default diff --git a/Tests/Config/test_config.py b/Tests/Config/test_config.py new file mode 100644 index 0000000..127cc81 --- /dev/null +++ b/Tests/Config/test_config.py @@ -0,0 +1,27 @@ +import os +import unittest +from configparser import ConfigParser + +class TestConfig(unittest.TestCase): + def setUp(self): + self.path = os.path.join('Config/logger.ini') + + def test_config_exists(self): + self.assertTrue(self.path) + + def test_config_is_valid(self): + parser = ConfigParser() + if self.test_config_exists: + parser.read(self.path) + + # Check if the required sections are present in the config + self.assertIn('formatters', parser.sections()) + self.assertIn('handler_console', parser.sections()) + + # Check if the required keys are present in the sections + self.assertIn('keys', parser['formatters']) + self.assertIn('level', parser['handler_console']) + + +if __name__ == "__main__": + unittest.main() From 6823b2f0be6bc97126995c529319f91fa8abe9d1 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 20:39:06 +1000 Subject: [PATCH 09/11] bugfix: ensure file exists before adding content --- Tests/Config/test_config.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/Config/test_config.py b/Tests/Config/test_config.py index 127cc81..f681000 100644 --- a/Tests/Config/test_config.py +++ b/Tests/Config/test_config.py @@ -6,6 +6,10 @@ class TestConfig(unittest.TestCase): def setUp(self): self.path = os.path.join('Config/logger.ini') + if not os.path.exists(os.path.join('Logs', 'traceback.log')): + with open(os.path.join('Logs', 'traceback.log')) as fp: + fp.close() + def test_config_exists(self): self.assertTrue(self.path) From adef2a78ddf4dfb3b7013178ffad98bf0e20a1e1 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 20:40:24 +1000 Subject: [PATCH 10/11] fix: ensure log directory exists before gitignore --- .gitignore | 1 + Tests/Config/test_config.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 37775fd..5b09833 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ Config/config.ini settings.json # C extensions *.so +Logs/ # Distribution / packaging .Python diff --git a/Tests/Config/test_config.py b/Tests/Config/test_config.py index f681000..764400d 100644 --- a/Tests/Config/test_config.py +++ b/Tests/Config/test_config.py @@ -6,6 +6,8 @@ class TestConfig(unittest.TestCase): def setUp(self): self.path = os.path.join('Config/logger.ini') + if not os.path.isdir('Logs'): + os.mkdir('Logs') if not os.path.exists(os.path.join('Logs', 'traceback.log')): with open(os.path.join('Logs', 'traceback.log')) as fp: fp.close() From cc971b10037bec2a4ce4c4facbfbe81ba3d4b2a3 Mon Sep 17 00:00:00 2001 From: Joshua Rose Date: Sun, 9 Apr 2023 20:48:22 +1000 Subject: [PATCH 11/11] feat/fix: fixed pending review --- Config/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Config/config.py b/Config/config.py index 30dc98d..1498aa7 100644 --- a/Config/config.py +++ b/Config/config.py @@ -28,7 +28,7 @@ def get_config(config_file_path): return config -def get_logging_config(): +def get_logging_config(file_location="Config/logger.ini"): """ Get logging configuration from configuration file. @@ -36,9 +36,9 @@ def get_logging_config(): :rtype: dict """ try: - open('Config/logger.ini') - except Exception: - print("File location invalid ") + open(file_location, "r") + except FileNotFoundError: + logging.critical(f"File location invalid: {file_location}") sys.exit(1) config = get_config('Config/logger.ini')