Synced files: - Quote wizard frontend (all components, hooks, types, config) - API updates (config, models, routers, schemas, services) - Client work (bg-builders, gurushow) - Scripts (BGB Lesley termination, CIPP, Datto, migration) - Temp files (Bardach contacts, VWP investigation, misc) - Credentials and session logs - Email service, PHP API, session logs Machine: ACG-M-L5090 Timestamp: 2026-03-10 19:11:00 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
"""Add Mail.Send permission to the app registration and grant admin consent."""
|
|
import json
|
|
import sys
|
|
import urllib.request
|
|
import urllib.parse
|
|
import urllib.error
|
|
import ssl
|
|
|
|
TENANT_ID = "5c53ae9f-7071-4248-b834-8685b646450f"
|
|
APP_ID = "fabb3421-8b34-484b-bc17-e46de9703418"
|
|
APP_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
|
|
GRAPH_APP_ID = "00000003-0000-0000-c000-000000000000" # Microsoft Graph
|
|
|
|
ctx = ssl.create_default_context()
|
|
|
|
|
|
def get_token():
|
|
data = urllib.parse.urlencode({
|
|
"client_id": APP_ID,
|
|
"scope": "https://graph.microsoft.com/.default",
|
|
"client_secret": APP_SECRET,
|
|
"grant_type": "client_credentials",
|
|
}).encode()
|
|
req = urllib.request.Request(
|
|
f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token",
|
|
data=data, method="POST"
|
|
)
|
|
with urllib.request.urlopen(req, context=ctx) as resp:
|
|
return json.loads(resp.read())["access_token"]
|
|
|
|
|
|
def graph_get(token, url):
|
|
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
|
with urllib.request.urlopen(req, context=ctx) as resp:
|
|
return json.loads(resp.read())
|
|
|
|
|
|
def graph_post(token, url, payload=None):
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
body = None
|
|
if payload:
|
|
headers["Content-Type"] = "application/json"
|
|
body = json.dumps(payload).encode()
|
|
else:
|
|
body = b""
|
|
req = urllib.request.Request(url, data=body, headers=headers, method="POST")
|
|
try:
|
|
with urllib.request.urlopen(req, context=ctx) as resp:
|
|
content = resp.read()
|
|
return json.loads(content) if content else {}
|
|
except urllib.error.HTTPError as e:
|
|
body = e.read().decode()
|
|
try:
|
|
return json.loads(body)
|
|
except:
|
|
return {"error": {"message": body, "code": str(e.code)}}
|
|
|
|
|
|
token = get_token()
|
|
print("[OK] Token acquired")
|
|
|
|
# Find our app's service principal
|
|
filter_val = urllib.parse.quote(f"appId eq '{APP_ID}'")
|
|
url = f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter={filter_val}"
|
|
data = graph_get(token, url)
|
|
if not data.get("value"):
|
|
print("[ERROR] Could not find our service principal")
|
|
print(json.dumps(data, indent=2)[:1000])
|
|
sys.exit(1)
|
|
our_sp_id = data["value"][0]["id"]
|
|
print(f"[OK] Our SP ID: {our_sp_id}")
|
|
|
|
# Find Microsoft Graph service principal
|
|
filter_val2 = urllib.parse.quote(f"appId eq '{GRAPH_APP_ID}'")
|
|
url2 = f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter={filter_val2}"
|
|
data2 = graph_get(token, url2)
|
|
graph_sp_id = data2["value"][0]["id"]
|
|
print(f"[OK] Graph SP ID: {graph_sp_id}")
|
|
|
|
# Find Mail.Send role
|
|
app_roles = data2["value"][0].get("appRoles", [])
|
|
mail_send_id = None
|
|
for role in app_roles:
|
|
if role.get("value") == "Mail.Send":
|
|
mail_send_id = role["id"]
|
|
break
|
|
|
|
if not mail_send_id:
|
|
print("[ERROR] Mail.Send role not found")
|
|
sys.exit(1)
|
|
print(f"[OK] Mail.Send role ID: {mail_send_id}")
|
|
|
|
# Grant the appRoleAssignment (admin consent)
|
|
payload = {
|
|
"principalId": our_sp_id,
|
|
"resourceId": graph_sp_id,
|
|
"appRoleId": mail_send_id
|
|
}
|
|
url3 = f"https://graph.microsoft.com/v1.0/servicePrincipals/{our_sp_id}/appRoleAssignments"
|
|
result = graph_post(token, url3, payload)
|
|
if result.get("id"):
|
|
print(f"[SUCCESS] Mail.Send permission granted! Assignment ID: {result['id']}")
|
|
elif "already exists" in str(result.get("error", {}).get("message", "")):
|
|
print("[INFO] Mail.Send permission already assigned")
|
|
else:
|
|
print(f"[RESULT] Response: {json.dumps(result, indent=2)[:1000]}")
|