From 6caa5a4e35c0f0a346d6a4aef47c12571d67511a Mon Sep 17 00:00:00 2001 From: "Christopher K. Hoadley" Date: Thu, 23 Apr 2020 06:58:28 -0500 Subject: [PATCH] Restructure all print output to use QueryNotifyPrint() object. Added start and finish methods to base QueryNotify() object in order to get the same type of output. The only thing not supported in this refactor is the exception text for an error status when we are in verbose mode. This is an area of future work: I think exception information like this would more properly be handled by the logging module. --- sherlock/notify.py | 195 +++++++++++++++++++++++++++++++++++++++++ sherlock/sherlock.py | 119 ++++--------------------- sherlock/tests/base.py | 5 +- 3 files changed, 213 insertions(+), 106 deletions(-) diff --git a/sherlock/notify.py b/sherlock/notify.py index 1046a682..67dde84e 100644 --- a/sherlock/notify.py +++ b/sherlock/notify.py @@ -4,6 +4,7 @@ This module defines the objects for notifying the caller about the results of queries. """ from result import QueryStatus +from colorama import Fore, Style, init class QueryNotify(): @@ -33,6 +34,25 @@ class QueryNotify(): return + def start(self, message=None): + """Notify Start. + + Notify method for start of query. This method will be called before + any queries are performed. This method will typically be + overridden by higher level classes that will inherit from it. + + Keyword Arguments: + self -- This object. + message -- Object that is used to give context to start + of query. + Default is None. + + Return Value: + Nothing. + """ + + return + def update(self, result): """Notify Update. @@ -52,6 +72,25 @@ class QueryNotify(): return + def finish(self, message=None): + """Notify Finish. + + Notify method for finish of query. This method will be called after + all queries have been performed. This method will typically be + overridden by higher level classes that will inherit from it. + + Keyword Arguments: + self -- This object. + message -- Object that is used to give context to start + of query. + Default is None. + + Return Value: + Nothing. + """ + + return + def __str__(self): """Convert Object To String. @@ -65,3 +104,159 @@ class QueryNotify(): return result + +def print_error(social_network, err, errstr, var, verbose=False, color=True): + if color: + print(Style.BRIGHT + Fore.WHITE + "[" + + Fore.RED + "-" + + Fore.WHITE + "]" + + Fore.GREEN + f" {social_network}:" + + Fore.RED + f" {errstr}" + + Fore.YELLOW + f" {err if verbose else var}") + else: + print(f"[-] {social_network}: {errstr} {err if verbose else var}") + + +def format_response_time(response_time, verbose): + return f" [{round(response_time * 1000)} ms]" if verbose else "" + + +def print_found(social_network, url, response_time, verbose=False, color=True): + if color: + print((Style.BRIGHT + Fore.WHITE + "[" + + Fore.GREEN + "+" + + Fore.WHITE + "]" + + format_response_time(response_time, verbose) + + Fore.GREEN + f" {social_network}:"), url) + else: + print(f"[+]{format_response_time(response_time, verbose)} {social_network}: {url}") + +def print_not_found(social_network, response_time, verbose=False, color=True): + if color: + print((Style.BRIGHT + Fore.WHITE + "[" + + Fore.RED + "-" + + Fore.WHITE + "]" + + format_response_time(response_time, verbose) + + Fore.GREEN + f" {social_network}:" + + Fore.YELLOW + " Not Found!")) + else: + print(f"[-]{format_response_time(response_time, verbose)} {social_network}: Not Found!") + +def print_invalid(social_network, msg, color=True): + """Print invalid search result.""" + if color: + print((Style.BRIGHT + Fore.WHITE + "[" + + Fore.RED + "-" + + Fore.WHITE + "]" + + Fore.GREEN + f" {social_network}:" + + Fore.YELLOW + f" {msg}")) + else: + print(f"[-] {social_network} {msg}") + + +class QueryNotifyPrint(QueryNotify): + """Query Notify Print Object. + + Query notify class that prints results. + """ + def __init__(self, result=None, verbose=False, print_found_only=False, + color=True): + """Create Query Notify Print Object. + + Contains information about a specific method of notifying the results + of a query. + + Keyword Arguments: + self -- This object. + result -- Object of type QueryResult() containing + results for this query. + verbose -- Boolean indicating whether to give verbose output. + print_found_only -- Boolean indicating whether to only print found sites. + color -- Boolean indicating whether to color terminal output + + Return Value: + Nothing. + """ + + # Colorama module's initialization. + init(autoreset=True) + + super().__init__(result) + self.verbose = verbose + self.print_found_only = print_found_only + self.color = color + + return + + def start(self, message): + """Notify Start. + + Will print the title to the standard output. + + Keyword Arguments: + self -- This object. + message -- String containing username that the series + of queries are about. + + Return Value: + Nothing. + """ + + title = "Checking username" + if self.color: + print(Style.BRIGHT + Fore.GREEN + "[" + + Fore.YELLOW + "*" + + Fore.GREEN + f"] {title}" + + Fore.WHITE + f" {message}" + + Fore.GREEN + " on:") + else: + print(f"[*] {title} {message} on:") + + return + + def update(self, result): + """Notify Update. + + Will print the query result to the standard output. + + Keyword Arguments: + self -- This object. + result -- Object of type QueryResult() containing + results for this query. + + Return Value: + Nothing. + """ + + self.result = result + + #Output to the terminal is desired. + if result.status == QueryStatus.CLAIMED: + print_found(self.result.site_name, self.result.site_url_user, self.result.query_time, self.verbose, self.color) + elif result.status == QueryStatus.AVAILABLE: + if not self.print_found_only: + print_not_found(self.result.site_name, self.result.query_time, self.verbose, self.color) + elif result.status == QueryStatus.UNKNOWN: + print_error(self.result.site_name, "Exception Text", self.result.context, "", self.verbose, self.color) + elif result.status == QueryStatus.ILLEGAL: + if self.print_found_only == False: + print_invalid(self.result.site_name, "Illegal Username Format For This Site!", self.color) + else: + #It should be impossible to ever get here... + raise ValueError(f"Unknown Query Status '{str(result.status)}' for " + f"site '{self.result.site_name}'") + + return + + def __str__(self): + """Convert Object To String. + + Keyword Arguments: + self -- This object. + + Return Value: + Nicely formatted string to get information about this object. + """ + result = str(self.result) + + return result diff --git a/sherlock/sherlock.py b/sherlock/sherlock.py index 658c20dd..c4f9e60d 100644 --- a/sherlock/sherlock.py +++ b/sherlock/sherlock.py @@ -20,13 +20,13 @@ from time import time import webbrowser import requests -from colorama import Fore, Style, init from requests_futures.sessions import FuturesSession from torrequest import TorRequest from result import QueryStatus from result import QueryResult from notify import QueryNotify +from notify import QueryNotifyPrint from sites import SitesInformation module_name = "Sherlock: Find Usernames Across Social Networks" @@ -98,66 +98,7 @@ class SherlockFuturesSession(FuturesSession): *args, **kwargs) -def print_info(title, info, color=True): - if color: - print(Style.BRIGHT + Fore.GREEN + "[" + - Fore.YELLOW + "*" + - Fore.GREEN + f"] {title}" + - Fore.WHITE + f" {info}" + - Fore.GREEN + " on:") - else: - print(f"[*] {title} {info} on:") - -def print_error(social_network, err, errstr, var, verbose=False, color=True): - if color: - print(Style.BRIGHT + Fore.WHITE + "[" + - Fore.RED + "-" + - Fore.WHITE + "]" + - Fore.GREEN + f" {social_network}:" + - Fore.RED + f" {errstr}" + - Fore.YELLOW + f" {err if verbose else var}") - else: - print(f"[-] {social_network}: {errstr} {err if verbose else var}") - - -def format_response_time(response_time, verbose): - return f" [{round(response_time * 1000)} ms]" if verbose else "" - - -def print_found(social_network, url, response_time, verbose=False, color=True): - if color: - print((Style.BRIGHT + Fore.WHITE + "[" + - Fore.GREEN + "+" + - Fore.WHITE + "]" + - format_response_time(response_time, verbose) + - Fore.GREEN + f" {social_network}:"), url) - else: - print(f"[+]{format_response_time(response_time, verbose)} {social_network}: {url}") - -def print_not_found(social_network, response_time, verbose=False, color=True): - if color: - print((Style.BRIGHT + Fore.WHITE + "[" + - Fore.RED + "-" + - Fore.WHITE + "]" + - format_response_time(response_time, verbose) + - Fore.GREEN + f" {social_network}:" + - Fore.YELLOW + " Not Found!")) - else: - print(f"[-]{format_response_time(response_time, verbose)} {social_network}: Not Found!") - -def print_invalid(social_network, msg, color=True): - """Print invalid search result.""" - if color: - print((Style.BRIGHT + Fore.WHITE + "[" + - Fore.RED + "-" + - Fore.WHITE + "]" + - Fore.GREEN + f" {social_network}:" + - Fore.YELLOW + f" {msg}")) - else: - print(f"[-] {social_network} {msg}") - - -def get_response(request_future, error_type, social_network, verbose=False, color=True): +def get_response(request_future, error_type, social_network): #Default for Response object if some failure occurs. response = None @@ -188,10 +129,9 @@ def get_response(request_future, error_type, social_network, verbose=False, colo return response, error_context, expection_text -def sherlock(username, site_data, query_notify, verbose=False, +def sherlock(username, site_data, query_notify, tor=False, unique_tor=False, - proxy=None, print_found_only=False, timeout=None, color=True, - print_output=True): + proxy=None, timeout=None): """Run Sherlock Analysis. Checks for existence of username on various social media sites. @@ -203,16 +143,11 @@ def sherlock(username, site_data, query_notify, verbose=False, query_notify -- Object with base type of QueryNotify(). This will be used to notify the caller about query results. - verbose -- Boolean indicating whether to give verbose output. tor -- Boolean indicating whether to use a tor circuit for the requests. unique_tor -- Boolean indicating whether to use a new tor circuit for each request. proxy -- String indicating the proxy URL - print_found_only -- Boolean indicating whether to only print found sites. timeout -- Time in seconds to wait before timing out request. Default is no timeout. - color -- Boolean indicating whether to color terminal output - print_output -- Boolean indicating whether the output should be - printed. Default is True. Return Value: Dictionary containing results from report. Key of dictionary is the name @@ -227,8 +162,9 @@ def sherlock(username, site_data, query_notify, verbose=False, response_text: Text that came back from request. May be None if there was an HTTP error when checking for existence. """ - if print_output == True: - print_info("Checking username", username, color) + + #Notify caller that we are starting the query. + query_notify.start(username) # Create session based on request methodology if tor or unique_tor: @@ -281,9 +217,6 @@ def sherlock(username, site_data, query_notify, verbose=False, regex_check = net_info.get("regexCheck") if regex_check and re.search(regex_check, username) is None: # No need to do the check at the site: this user name is not allowed. - if (print_output == True) and not print_found_only: - print_invalid(social_network, "Illegal Username Format For This Site!", color) - results_site['status'] = QueryResult(username, social_network, url, @@ -365,9 +298,7 @@ def sherlock(username, site_data, query_notify, verbose=False, future = net_info["request_future"] r, error_text, expection_text = get_response(request_future=future, error_type=error_type, - social_network=social_network, - verbose=verbose, - color=color) + social_network=social_network) #Get response time for response of our request. try: @@ -448,24 +379,6 @@ def sherlock(username, site_data, query_notify, verbose=False, #Notify caller about results of query. query_notify.update(result) - if print_output == True: - #Output to the terminal is desired. - if result.status == QueryStatus.CLAIMED: - print_found(social_network, url, response_time, verbose, color) - elif result.status == QueryStatus.AVAILABLE: - if not print_found_only: - print_not_found(social_network, response_time, verbose, color) - elif result.status == QueryStatus.UNKNOWN: - print_error(social_network, expection_text, error_text, "", verbose, color) - elif result.status == QueryStatus.ILLEGAL: - if not print_found_only: - print_invalid(social_network, "Illegal Username Format For This Site!", color) - else: - #It should be impossible to ever get here... - raise ValueError(f"Unknown Query Status '{str(result.status)}' for " - f"site '{social_network}'") - - # Save status of request results_site['status'] = result @@ -475,6 +388,10 @@ def sherlock(username, site_data, query_notify, verbose=False, # Add this site's results into final dictionary with all of the other results. results_total[social_network] = results_site + + #Notify caller that all queries are finished. + query_notify.finish() + return results_total @@ -504,8 +421,6 @@ def timeout_check(value): def main(): - # Colorama module's initialization. - init(autoreset=True) version_string = f"%(prog)s {__version__}\n" + \ f"{requests.__description__}: {requests.__version__}\n" + \ @@ -651,7 +566,10 @@ def main(): #Create notify object for query results. - query_notify = QueryNotify() + query_notify = QueryNotifyPrint(result=None, + verbose=args.verbose, + print_found_only=args.print_found_only, + color=not args.no_color) # Run report on all specified users. for username in args.username: @@ -660,13 +578,10 @@ def main(): results = sherlock(username, site_data, query_notify, - verbose=args.verbose, tor=args.tor, unique_tor=args.unique_tor, proxy=args.proxy, - print_found_only=args.print_found_only, - timeout=args.timeout, - color=not args.no_color) + timeout=args.timeout) if args.output: result_file = args.output diff --git a/sherlock/tests/base.py b/sherlock/tests/base.py index b497e7f6..999be467 100644 --- a/sherlock/tests/base.py +++ b/sherlock/tests/base.py @@ -118,12 +118,9 @@ class SherlockBaseTest(unittest.TestCase): results = sherlock.sherlock(username, site_data, self.query_notify, - verbose=self.verbose, tor=self.tor, unique_tor=self.unique_tor, - timeout=self.timeout, - color=False, - print_output=False + timeout=self.timeout ) for site, result in results.items(): with self.subTest(f"Checking Username '{username}' "