diff --git a/README.md b/README.md index 578aa51..3de146d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![logo](https://user-images.githubusercontent.com/74001397/175805580-fffc96d4-e0ef-48bb-a55c-80b2da3e714d.png) -A framework for gathering open-source intelligence on GitHub users, repositories and organizations +A framework for gathering open-source intelligence on GitHub users, repositories and organisations [![Upload Python Package](https://github.com/bellingcat/octosuite/actions/workflows/python-publish.yml/badge.svg)](https://github.com/bellingcat/octosuite/actions/workflows/python-publish.yml) [![CodeQL](https://github.com/bellingcat/octosuite/actions/workflows/codeql.yml/badge.svg)](https://github.com/bellingcat/octosuite/actions/workflows/codeql.yml) @@ -20,10 +20,10 @@ A framework for gathering open-source intelligence on GitHub users, repositories [Refer to the Wiki](https://github.com/bellingcat/octosuite/wiki) for installation instructions, in addition to all other documentation. # Features -- [x] Fetches an organization's profile information +- [x] Fetches an organisation's profile information - [x] Fetches an oganization's events -- [x] Returns an organization's repositories -- [x] Returns an organization's public members +- [x] Returns an organisation's repositories +- [x] Returns an organisation's public members - [x] Fetches a repository's information - [x] Returns a repository's contributors - [x] Returns a repository's languages @@ -33,12 +33,12 @@ A framework for gathering open-source intelligence on GitHub users, repositories - [x] Returns a list of files in a specified path of a repository - [x] Fetches a user's profile information - [x] Returns a user's gists -- [x] Returns organizations that a user owns/belongs to +- [x] Returns organisations that a user owns/belongs to - [x] Fetches a user's events - [x] Fetches a list of users followed by the target - [x] Fetches a user's followers - [x] Checks if user A follows user B -- [x] Checks if user is a public member of an organizations +- [x] Checks if user is a public member of an organisations - [x] Gets a user's subscriptions - [x] Searches users - [x] Searches repositories diff --git a/octosuite/config.py b/octosuite/config.py index b6a49e4..fca805d 100644 --- a/octosuite/config.py +++ b/octosuite/config.py @@ -25,14 +25,14 @@ def usage(): octosuite --method user_repos --username - Get Organi[sz]ation Profile Info + Get Organisation Profile Info ----------------------------- - octosuite --method org_profile --organization + octosuite --method org_profile --organisation Get Organi[sz]ation Repos ----------------------------- - octosuite --method org_repos --organization + octosuite --method org_repos --organisation Get Repo Profile Info @@ -134,7 +134,7 @@ def create_parser(): 'clear_logs', 'view_csv', 'read_csv', 'delete_csv', 'clear_csv', 'about', 'author']) parser.add_argument('-u', '--username', help='username') parser.add_argument('-uB', '--username_b', help='username_B (used with user_follows)') - parser.add_argument('-o', '--organization', '--organisation', help='organi[sz]ation name') + parser.add_argument('-o', '--organisation', '--organization', help='organisation name') parser.add_argument('-r', '--repository', help='repository name') parser.add_argument('-p', '--path_name', help='path name (used with repo_path_contents)') parser.add_argument('-q', '--query', help='query (used with search methods)') diff --git a/octosuite/csv_loggers.py b/octosuite/csv_loggers.py index c518b82..2449f8a 100644 --- a/octosuite/csv_loggers.py +++ b/octosuite/csv_loggers.py @@ -11,14 +11,14 @@ from octosuite.message_prefixes import PROMPT, WARNING, POSITIVE, NEGATIVE, INFO def log_org_profile(response): org_profile_fields = ['Profile photo', 'Name', 'Username', 'ID', 'Node ID', 'Email', 'About', 'Location', 'Blog', 'Followers', 'Following', 'Twitter handle', 'Gists', 'Repositories', 'Account type', - 'Is verified?', 'Has organization projects?', 'Has repository projects?', 'Created at', + 'Is verified?', 'Has organisation projects?', 'Has repository projects?', 'Created at', 'Updated at'] org_profile_row = [response.json()['avatar_url'], response.json()['name'], response.json()['login'], response.json()['id'], response.json()['node_id'], response.json()['email'], response.json()['description'], response.json()['location'], response.json()['blog'], response.json()['followers'], response.json()['following'], response.json()['twitter_username'], response.json()['public_gists'], response.json()['public_repos'], response.json()['type'], - response.json()['is_verified'], response.json()['has_organization_projects'], + response.json()['is_verified'], response.json()['has_organisation_projects'], response.json()['has_repository_projects'], response.json()['created_at'], response.json()['updated_at']] @@ -34,7 +34,7 @@ def log_org_profile(response): # Creating a .csv file of a user' profile def log_user_profile(response): user_profile_fields = ['Profile photo', 'Name', 'Username', 'ID', 'Node ID', 'Bio', 'Blog', 'Location', 'Followers', - 'Following', 'Twitter handle', 'Gists', 'Repositories', 'Organization', 'Is hireable?', + 'Following', 'Twitter handle', 'Gists', 'Repositories', 'organisation', 'Is hireable?', 'Is site admin?', 'Joined at', 'Updated at'] user_profile_row = [response.json()['avatar_url'], response.json()['name'], response.json()['login'], response.json()['id'], response.json()['node_id'], response.json()['bio'], @@ -184,12 +184,12 @@ def log_repo_contributors(contributor, repo_name): xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}") -# Create .csv for organization' events -def log_repo_events(event, organization): +# Create .csv for organisation' events +def log_repo_events(event, organisation): org_event_fields = ['ID', 'Type', 'Created at', 'Payload'] org_event_row = [event['id'], event['type'], event['created_at'], event['payload']] - with open(os.path.join("output", f"{organization}_event_{event['id']}.csv"), 'w') as file: + with open(os.path.join("output", f"{organisation}_event_{event['id']}.csv"), 'w') as file: write_csv = csv.writer(file) write_csv.writerow(org_event_fields) write_csv.writerow(org_event_row) @@ -198,8 +198,8 @@ def log_repo_events(event, organization): xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}") -# Create .csv for organization' repositories -def log_org_repos(repository, organization): +# Create .csv for organisation' repositories +def log_org_repos(repository, organisation): org_repo_fields = ['Name', 'ID', 'About', 'Forks', 'Stars', 'Watchers', 'License', 'Branch', 'Visibility', 'Language(s)', 'Open issues', 'Topics', 'Homepage', 'Clone URL', 'SSH URL', 'Is fork?', 'Is forkable?', 'Is private?', 'Is archived?', 'Is template?', 'Has wiki?', 'Has pages?', @@ -213,7 +213,7 @@ def log_org_repos(repository, organization): repository['has_projects'], repository['has_issues'], repository['has_downloads'], repository['pushed_at'], repository['created_at'], repository['updated_at']] - with open(os.path.join("output", f"{repository['name']}_repository_of_{organization}.csv"), 'w') as file: + with open(os.path.join("output", f"{repository['name']}_repository_of_{organisation}.csv"), 'w') as file: write_csv = csv.writer(file) write_csv.writerow(org_repo_fields) write_csv.writerow(org_repo_row) @@ -335,13 +335,13 @@ def log_user_subscriptions(repository, username): xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}") -# .csv for user organizations -def log_user_orgs(organization, username): +# .csv for user organisations +def log_user_orgs(organisation, username): user_org_fields = ['Profile photo', 'Name', 'ID', 'Node ID', 'URL', 'About'] - user_org_row = [organization['avatar_url'], organization['login'], organization['id'], organization['node_id'], - organization['url'], organization['description']] + user_org_row = [organisation['avatar_url'], organisation['login'], organisation['id'], organisation['node_id'], + organisation['url'], organisation['description']] - with open(os.path.join("output", f"{organization['login']}_{username}.csv"), 'w') as file: + with open(os.path.join("output", f"{organisation['login']}_{username}.csv"), 'w') as file: write_csv = csv.writer(file) write_csv.writerow(user_org_fields) write_csv.writerow(user_org_row) diff --git a/octosuite/helper.py b/octosuite/helper.py index e30afcc..0136949 100644 --- a/octosuite/helper.py +++ b/octosuite/helper.py @@ -70,7 +70,7 @@ def user_command(): user_cmd_table.add_row("email", "Return a target's email") user_cmd_table.add_row("profile", "Get a target's profile info") user_cmd_table.add_row("gists", "Return a users's gists") - user_cmd_table.add_row("orgs", "Return organizations that a target belongs to/owns") + user_cmd_table.add_row("orgs", "Return organisations that a target belongs to/owns") user_cmd_table.add_row("repos", "Return a target's repositories") user_cmd_table.add_row("events", "Return a target's events") user_cmd_table.add_row("follows", "Check if user(A) follows user(B)") @@ -87,13 +87,13 @@ def org_command(): org_cmd_table = Table(show_header=True, header_style=header_title) org_cmd_table.add_column("Command", style="dim") org_cmd_table.add_column("Description") - org_cmd_table.add_row("profile", "Get a target organization' profile info") - org_cmd_table.add_row("repos", "Return a target organization' repositories") - org_cmd_table.add_row("events", "Return a target organization' events") - org_cmd_table.add_row("member", "Check if a specified user is a public member of the target organization") + org_cmd_table.add_row("profile", "Get a target organisation' profile info") + org_cmd_table.add_row("repos", "Return a target organisation' repositories") + org_cmd_table.add_row("events", "Return a target organisation' events") + org_cmd_table.add_row("member", "Check if a specified user is a public member of the target organisation") syntax = f"{green}org:{reset}" - xprint(f"{usage_text.format(syntax, 'organization investigation(s)')}") + xprint(f"{usage_text.format(syntax, 'organisation investigation(s)')}") xprint(org_cmd_table) @@ -159,7 +159,7 @@ def help_command(): help_sub_cmd_table.add_column("Description") help_sub_cmd_table.add_row("csv", "List all csv management commands") help_sub_cmd_table.add_row("logs", "List all logs management commands") - help_sub_cmd_table.add_row("org", "List all organization investigation commands") + help_sub_cmd_table.add_row("org", "List all organisation investigation commands") help_sub_cmd_table.add_row("user", "List all users investigation commands") help_sub_cmd_table.add_row("repo", "List all repository investigation commands") help_sub_cmd_table.add_row("search", "List all target discovery commands") diff --git a/octosuite/log_roller.py b/octosuite/log_roller.py index 216b965..a234d52 100644 --- a/octosuite/log_roller.py +++ b/octosuite/log_roller.py @@ -14,7 +14,7 @@ file_downloading = "Downloading: {}" file_downloaded = "Downloaded: downloads/{}" info_not_found = "Information not found: {}, {}, {}" user_not_found = "User not found: @{}" -org_not_found = "Organization not found: @{}" +org_not_found = "organisation not found: @{}" repo_or_user_not_found = "Repository or User not found: {}, @{}" prompt_log_csv = "Would you like to log this output to a .csv file?" logged_to_csv = "Output logged: {}" diff --git a/octosuite/octosuite.py b/octosuite/octosuite.py index c758fb0..0dca337 100644 --- a/octosuite/octosuite.py +++ b/octosuite/octosuite.py @@ -182,7 +182,7 @@ def about(): about_text = f""" OCTOSUITE © 2023 Richard Mwewa -An advanced and lightning fast framework for gathering open-source intelligence on GitHub users and organizations. +An advanced and lightning fast framework for gathering open-source intelligence on GitHub users and organisations. Read the wiki: https://github.com/bellingcat/octosuite/wiki GitHub REST API documentation: https://docs.github.com/rest @@ -318,12 +318,12 @@ class Octosuite: 'sha': 'SHA', 'html_url': 'URL'} - # Organization attributes + # organisation attributes self.org_attrs = ['avatar_url', 'login', 'id', 'node_id', 'email', 'description', 'blog', 'location', 'followers', 'following', 'twitter_username', 'public_gists', 'public_repos', 'type', 'is_verified', - 'has_organization_projects', 'has_repository_projects', 'created_at', 'updated_at'] - # Organization attribute dictionary + 'has_organisation_projects', 'has_repository_projects', 'created_at', 'updated_at'] + # organisation attribute dictionary self.org_attr_dict = {'avatar_url': 'Profile Photo', 'login': 'Username', 'id': 'ID', @@ -339,7 +339,7 @@ class Octosuite: 'public_repos': 'Repositories', 'type': 'Account type', 'is_verified': 'Is verified?', - 'has_organization_projects': 'Has organization projects?', + 'has_organisation_projects': 'Has organisation projects?', 'has_repository_projects': 'Has repository projects?', 'created_at': 'Created at', 'updated_at': 'Updated at'} @@ -415,7 +415,7 @@ class Octosuite: 'twitter_username': 'Twitter Handle', 'public_gists': 'Gists (public)', 'public_repos': 'Repositories (public)', - 'company': 'Organization', + 'company': 'organisation', 'hireable': 'Is hireable?', 'site_admin': 'Is site admin?', 'created_at': 'Joined at', @@ -499,7 +499,7 @@ class Octosuite: 'created_at': 'Created at', 'updated_at': 'Updated at'} - # User organizations attributes + # User organisations attributes self.user_orgs_attrs = ['avatar_url', 'id', 'node_id', 'url', 'description'] self.user_orgs_attr_dict = {'avatar_url': 'Profile Photo', 'id': 'ID', @@ -535,15 +535,15 @@ class Octosuite: xprint(f"{username}: {email}") break - # Fetching organization info + # Fetching organisation info def org_profile(self): - if args.organization: - organization = args.organization + if args.organisation: + organisation = args.organisation else: - organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}") - response = requests.get(f"{self.endpoint}/orgs/{organization}") + organisation = Prompt.ask(f"{white}@{green}Organisation{reset}") + response = requests.get(f"{self.endpoint}/orgs/{organisation}") if response.status_code == 404: - xprint(f"{NEGATIVE} {org_not_found.format(organization)}") + xprint(f"{NEGATIVE} {org_not_found.format(organisation)}") elif response.status_code == 200: org_profile_tree = Tree(f"\n{response.json()['name']}") for attr in self.org_attrs: @@ -755,17 +755,17 @@ class Octosuite: else: xprint(response.json()) - # Fetching organization repositories + # Fetching organisation repositories def org_repos(self): - if args.organization and args.limit: - organization = args.organization + if args.organisation and args.limit: + organisation = args.organisation limit = args.limit else: - organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}") - limit = Prompt.ask(limit_output.format("organization repositories")) - response = requests.get(f"{self.endpoint}/orgs/{organization}/repos?per_page={limit}") + organisation = Prompt.ask(f"{white}@{green}Organisation{reset}") + limit = Prompt.ask(limit_output.format("organisation repositories")) + response = requests.get(f"{self.endpoint}/orgs/{organisation}/repos?per_page={limit}") if response.status_code == 404: - xprint(f"{NEGATIVE} {org_not_found.format(organization)}") + xprint(f"{NEGATIVE} {org_not_found.format(organisation)}") elif response.status_code == 200: for repository in response.json(): repos_tree = Tree("\n" + repository['full_name']) @@ -774,21 +774,21 @@ class Octosuite: xprint(repos_tree) if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}") == "yes": - log_org_repos(repository, organization) + log_org_repos(repository, organisation) else: xprint(response.json()) - # organization events + # organisation events def org_events(self): - if args.organization and args.limit: - organization = args.organization + if args.organisation and args.limit: + organisation = args.organisation limit = args.limit else: - organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}") - limit = Prompt.ask(limit_output.format("organization events")) - response = requests.get(f"{self.endpoint}/orgs/{organization}/events?per_page={limit}") + organisation = Prompt.ask(f"{white}@{green}Organisation{reset}") + limit = Prompt.ask(limit_output.format("organisation events")) + response = requests.get(f"{self.endpoint}/orgs/{organisation}/events?per_page={limit}") if response.status_code == 404: - xprint(f"{NEGATIVE} {org_not_found.format(organization)}") + xprint(f"{NEGATIVE} {org_not_found.format(organisation)}") elif response.status_code == 200: for event in response.json(): events_tree = Tree("\n" + event['id']) @@ -796,21 +796,21 @@ class Octosuite: events_tree.add(f"Created at: {event['created_at']}") xprint(events_tree) xprint(event['payload']) - # log_org_events(event, organization) + # log_org_events(event, organisation) else: xprint(response.json()) - # organization member + # organisation member def org_member(self): - if args.organization and args.username: - organization = args.organization + if args.organisation and args.username: + organisation = args.organisation username = args.username else: - organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}") + organisation = Prompt.ask(f"{white}@{green}Organisation{reset}") username = Prompt.ask(f"{white}@{green}Username{reset}") - response = requests.get(f"{self.endpoint}/orgs/{organization}/public_members/{username}") + response = requests.get(f"{self.endpoint}/orgs/{organisation}/public_members/{username}") if response.status_code == 204: - xprint(f"{POSITIVE} User ({username}) is a public member of the organization -> ({organization})") + xprint(f"{POSITIVE} User ({username}) is a public member of the organisation -> ({organisation})") else: xprint(f"{NEGATIVE} {response.json()['message']}") @@ -862,28 +862,28 @@ class Octosuite: else: xprint(response.json()) - # Fetching a list of organizations that a user owns or belongs to + # Fetching a list of organisations that a user owns or belongs to def user_orgs(self): if args.username and args.limit: username = args.username limit = args.limit else: username = Prompt.ask(f"{white}@{green}Username{reset}") - limit = Prompt.ask(limit_output.format("user organizations")) + limit = Prompt.ask(limit_output.format("user organisations")) response = requests.get(f"{self.endpoint}/users/{username}/orgs?per_page={limit}") if not response.json(): - xprint(f"{NEGATIVE} User ({username}) does not (belong to/own) any organizations.") + xprint(f"{NEGATIVE} User ({username}) does not (belong to/own) any organisations.") elif response.status_code == 404: xprint(f"{NEGATIVE} {user_not_found.format(username)}") elif response.status_code == 200: - for organization in response.json(): - org_tree = Tree("\n" + organization['login']) + for organisation in response.json(): + org_tree = Tree("\n" + organisation['login']) for attr in self.user_orgs_attrs: - org_tree.add(f"{self.user_orgs_attr_dict[attr]}: {organization[attr]}") + org_tree.add(f"{self.user_orgs_attr_dict[attr]}: {organisation[attr]}") xprint(org_tree) if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"): - log_user_orgs(organization, username) + log_user_orgs(organisation, username) else: xprint(response.json())