Skip to content

Instantly share code, notes, and snippets.

@faisalfs10x
Last active January 9, 2024 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save faisalfs10x/6951175a4857ae7ecb1412a1ea3d13f2 to your computer and use it in GitHub Desktop.
Save faisalfs10x/6951175a4857ae7ecb1412a1ea3d13f2 to your computer and use it in GitHub Desktop.
Retrieve user or group information from Microsoft 365 via Microsoft Graph API
#!/usr/bin/python3
"""
# GitHub: https://github.com/faisalfs10x
# Usage
M365-msgraphenum.py -auth <token_file> -mode user
M365-msgraphenum.py -auth <token_file> -mode roles
M365-msgraphenum.py -auth <token_file> -mode group
M365-msgraphenum.py -auth <token_file> -mode groupmember -gid <group_id>
"""
import argparse
import requests
import json
import pandas as pd
from datetime import datetime
import jwt
import base64
def read_bearer_token(auth_token_file):
with open(auth_token_file, 'r') as file:
return file.read().strip()
def get_headers(bearer_token):
return {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0',
'Authorization': f'Bearer {bearer_token}',
}
def get_user_profile(headers):
profile_url = 'https://graph.microsoft.com/v1.0/me'
resp_profile = requests.get(profile_url, headers=headers)
display_name = resp_profile.json().get('displayName')
user_principal_name = resp_profile.json().get('userPrincipalName')
if display_name is not None and user_principal_name is not None:
print(f'\n[+] Successfully authenticated with {display_name} | {user_principal_name} [+]')
else:
print(f'\n[-] Failed to authenticate. Pls check the token file [-]\n')
exit()
return display_name, user_principal_name
def get_group_display_name(group_id, headers):
group_url = f'https://graph.microsoft.com/v1.0/groups/{group_id}'
response = requests.get(group_url, headers=headers)
return response.json().get("displayName")
def process_user_data(data):
users_data = []
user_count = 0
for user in data.get('value', []):
user_data = {
'id': user.get('id'),
'displayName': user.get('displayName'),
'businessPhones': user.get('businessPhones'),
'givenName': user.get('givenName'),
'jobTitle': user.get('jobTitle'),
'mail': user.get('mail'),
'mobilePhone': user.get('mobilePhone'),
'officeLocation': user.get('officeLocation'),
'preferredLanguage': user.get('preferredLanguage'),
'surname': user.get('surname'),
'userPrincipalName': user.get('userPrincipalName'),
}
users_data.append(user_data)
user_count += 1
print(f'{user_count}. ID: {user_data["id"]} | Name: {user_data["displayName"]} | UPN: {user_data["userPrincipalName"]}')
return users_data, user_count
def process_group_data(data):
groups_data = []
group_count = 0
for group in data.get('value', []):
group_data = {
'id': group.get('id'),
'displayName': group.get('displayName'),
'description': group.get('description'),
'isAssignableToRole': group.get('isAssignableToRole'),
'mail': group.get('mail'),
'securityEnabled': group.get('securityEnabled'),
'onPremisesDomainName': group.get('onPremisesDomainName'),
'onPremisesSamAccountName': group.get('onPremisesSamAccountName'),
'onPremisesSecurityIdentifier': group.get('onPremisesSecurityIdentifier'),
'securityIdentifier': group.get('securityIdentifier'),
'onPremisesSyncEnabled': group.get('onPremisesSyncEnabled'),
'proxyAddresses': group.get('proxyAddresses'),
}
groups_data.append(group_data)
group_count += 1
print(f'{group_count}. ID: {group_data["id"]} | Name: {group_data["displayName"]}')
return groups_data, group_count
def get_tenant_id(bearer_token):
# Decode the JWT token to obtain the Tenant ID
token_segments = bearer_token.split('.')
decoded_token = json.loads(base64.b64decode(token_segments[1] + '==' * (-len(token_segments[1]) % 4)).decode('utf-8'))
return decoded_token.get('tid')
def get_organization_name(headers):
organization_url = 'https://graph.microsoft.com/v1.0/organization'
response = requests.get(organization_url, headers=headers)
organization_data = response.json()
organization_name = organization_data['value'][0].get('displayName', None)
return organization_name
def retrieve_users(auth_token_file):
bearer_token = read_bearer_token(auth_token_file)
headers = get_headers(bearer_token)
mydisplay_name, myuserPrincipalName = get_user_profile(headers)
org_name = get_organization_name(headers)
current_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")
#excel_file_path = f'userlist_{org_name}_{current_datetime}.xlsx'
users_data = []
user_count = 0
page_count = 2
url = 'https://graph.microsoft.com/v1.0/users'
org_name = get_organization_name(headers)
tenant_id = get_tenant_id(bearer_token)
print(f'\n[+] Gather list of users in [{org_name}] with TenantID: [{tenant_id}] [+]\n')
while url:
response = requests.get(url, headers=headers)
data = response.json()
# Extract and process relevant data
user_data, count = process_user_data(data)
users_data.extend(user_data)
user_count += count
# Print to console when the next page is found
print(f'\n[X] Processing page {page_count} of [{org_name}] [X]')
page_count += 1
# Check if there is the next page
url = data.get('@odata.nextLink')
# Create a DataFrame from the extracted data
df = pd.DataFrame(users_data)
# Export the DataFrame to Excel file
excel_file_path = f'userlist({user_count})_{org_name}_{current_datetime}.xlsx'
df.to_excel(excel_file_path, index=False)
print(f'\nData exported to {excel_file_path}')
print(f'Total number of users: {user_count}')
def retrieve_groups(auth_token_file):
bearer_token = read_bearer_token(auth_token_file)
headers = get_headers(bearer_token)
mydisplay_name, myuserPrincipalName = get_user_profile(headers)
org_name = get_organization_name(headers)
current_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")
#excel_file_path = f'grouplist_{org_name}_{current_datetime}.xlsx'
groups_data = []
group_count = 0
page_count = 2
url = 'https://graph.microsoft.com/v1.0/groups'
org_name = get_organization_name(headers)
tenant_id = get_tenant_id(bearer_token)
print(f'\n[+] Gather list of groups in [{org_name}] with TenantID: [{tenant_id}] [+]\n')
while url:
response = requests.get(url, headers=headers)
data = response.json()
# Extract and process relevant data
group_data, count = process_group_data(data)
groups_data.extend(group_data)
group_count += count
# Print to console when the next page is found
print(f'\n[X] Processing page {page_count} of [{org_name}] [X]')
page_count += 1
# Check if there is the next page
url = data.get('@odata.nextLink')
# Create a DataFrame from the extracted data
df = pd.DataFrame(groups_data)
# Export the DataFrame to Excel file
excel_file_path = f'grouplist({group_count})_{org_name}_{current_datetime}.xlsx'
df.to_excel(excel_file_path, index=False)
print(f'\nData exported to {excel_file_path}')
print(f'Total number of groups: {group_count}')
def retrieve_directoryroles(auth_token_file):
bearer_token = read_bearer_token(auth_token_file)
headers = get_headers(bearer_token)
mydisplay_name, myuserPrincipalName = get_user_profile(headers)
org_name = get_organization_name(headers)
current_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")
org_name = get_organization_name(headers)
tenant_id = get_tenant_id(bearer_token)
print(f'\n[+] Gather list of directory roles in [{org_name}] with TenantID: [{tenant_id}] [+]\n')
url = "https://graph.microsoft.com/v1.0/directoryRoles"
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
# Extract the values
roles = data.get("value", [])
# Create an empty list to store the data
all_data = []
# Process each role and extract id, displayName and Description
for role_num, role in enumerate(roles, start=1):
role_id = role.get("id")
display_name = role.get("displayName")
description = role.get("description")
# Request to the members endpoint for each role
members_url = f'https://graph.microsoft.com/v1.0/directoryRoles/{role_id}/members'
members_response = requests.get(members_url, headers=headers)
if members_response.status_code == 200:
members_data = members_response.json()
# Extract member information
members = members_data.get("value", [])
# Append role and member information to the list
for member_num, member in enumerate(members, start=1):
member_info = {"Role ID": role_id, "Display Name": display_name, "Description" : description}
member_info.update(member)
all_data.append(member_info)
# Print member information to console
print(f"\n{role_num}-{member_num} - Role ID: {role_id}, Display Name: {display_name}, Member Information:")
for key, value in member.items():
print(f" {key}: {value}")
print("-----------------------------------------------------")
else:
print(f"Error getting members for Role ID {role_id}: {members_response.status_code}")
else:
print(f"Error: {response.status_code}")
# Create a DataFrame from the list of data
df = pd.DataFrame(all_data)
# Print DataFrame to console
print("\nDataFrame:")
print(df)
# Save the DataFrame to an Excel file
excel_file_path = f'directoryrole_members_{org_name}_{current_datetime}.xlsx'
df.to_excel(excel_file_path, index=False)
print(f"\nData saved to {excel_file_path}")
def retrieve_users_in_group(group_id, auth_token_file):
bearer_token = read_bearer_token(auth_token_file)
headers = get_headers(bearer_token)
mydisplay_name, myuserPrincipalName = get_user_profile(headers)
users_data = []
user_count = 0
page_count = 2
org_name = get_organization_name(headers)
tenant_id = get_tenant_id(bearer_token)
print(f'\n[+] Organization [{org_name}] with TenantID: [{tenant_id}] [+]')
group_display_name = get_group_display_name(group_id, headers)
current_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")
invalid_chars = {'/', ':', '*', '?', '"', '<', '>', '|'}
sanitized_group_name = ''.join('_' if char in invalid_chars else char for char in group_display_name)
#excel_file_path = f'member_{sanitized_group_name}_group_{current_datetime}.xlsx'
print(f'\n[+] Gather list of users in group: {group_display_name} [+]')
endpoint_url = f'https://graph.microsoft.com/v1.0/groups/{group_id}/members?$count=true'
while endpoint_url:
response = requests.get(endpoint_url, headers=headers)
data = response.json()
# Extract and process relevant data
user_data, count = process_user_data(data)
users_data.extend(user_data)
user_count += count
# Print to console when the next page is found
print(f'\n[X] Processing page {page_count} of [{group_display_name}] [X]')
page_count += 1
# Check if there is the next page
endpoint_url = data.get('@odata.nextLink')
# Create a DataFrame from the extracted data
df = pd.DataFrame(users_data)
# Add a title row with the group display name to the DataFrame
title_row = pd.DataFrame({'Title': [group_display_name]})
df = pd.concat([title_row, df], ignore_index=False)
# Export the DataFrame to Excel file
excel_file_path = f'member({user_count})_{sanitized_group_name}_group_{current_datetime}.xlsx'
df.to_excel(excel_file_path, index=False)
print(f'\nData exported to {excel_file_path}')
print(f'Total number of users in group: {user_count}')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Retrieve user or group information from Microsoft 365')
parser.add_argument('-auth', '--auth_token_file', type=str, required=True, help='File containing the Bearer token')
parser.add_argument('-mode', '--mode', type=str, choices=['user', 'group', 'groupmember', 'roles'], required=True, help='Operation mode')
parser.add_argument('-gid', '--group_id', type=str, help='ID of the group (for retrieving users in a group)')
args = parser.parse_args()
if args.mode == 'user':
retrieve_users(args.auth_token_file)
elif args.mode == 'group':
retrieve_groups(args.auth_token_file)
elif args.mode == 'groupmember' and args.group_id:
retrieve_users_in_group(args.group_id, args.auth_token_file)
elif args.mode == 'roles':
retrieve_directoryroles(args.auth_token_file)
else:
print("[-] Invalid mode or missing -gid [-]")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment