sync: Auto-sync from ACG-M-L5090 at 2026-03-10 19:11:00
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>
This commit is contained in:
129
temp/bardach_onboard.py
Normal file
129
temp/bardach_onboard.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import urllib.request, urllib.parse, json, sys
|
||||
|
||||
CLAUDE_APP = "fabb3421-8b34-484b-bc17-e46de9703418"
|
||||
CLAUDE_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
|
||||
TENANT_ID = "dd4a82e8-85a3-44ac-8800-07945ab4d95f"
|
||||
TENANT = "bardach.net"
|
||||
|
||||
def get_token(tid, cid, secret, scope):
|
||||
data = urllib.parse.urlencode({
|
||||
'client_id': cid, 'client_secret': secret,
|
||||
'scope': scope, 'grant_type': 'client_credentials'
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"https://login.microsoftonline.com/{tid}/oauth2/v2.0/token",
|
||||
data=data, method='POST')
|
||||
with urllib.request.urlopen(req) 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) as resp:
|
||||
return json.loads(resp.read())
|
||||
|
||||
def graph_post(token, url, body):
|
||||
data = json.dumps(body).encode()
|
||||
req = urllib.request.Request(url, data=data, method='POST',
|
||||
headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'})
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
return json.loads(resp.read())
|
||||
|
||||
# Step 1: Get Graph token
|
||||
print("[STEP 1] Getting Graph token...")
|
||||
token = get_token(TENANT_ID, CLAUDE_APP, CLAUDE_SECRET, "https://graph.microsoft.com/.default")
|
||||
print("[OK] Graph token acquired")
|
||||
|
||||
# Step 2: Find Claude SP
|
||||
print("\n[STEP 2] Finding Claude SP...")
|
||||
sp_filter = urllib.parse.quote(f"appId eq '{CLAUDE_APP}'")
|
||||
sp_result = graph_get(token, f"https://graph.microsoft.com/v1.0/servicePrincipals?$filter={sp_filter}&$select=id,displayName")
|
||||
if sp_result.get('value'):
|
||||
sp = sp_result['value'][0]
|
||||
sp_id = sp['id']
|
||||
print(f"[OK] SP: {sp['displayName']} (ID: {sp_id})")
|
||||
else:
|
||||
print("[ERROR] Claude SP not found")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Check granted app role assignments
|
||||
print("\n[STEP 3] Checking granted permissions...")
|
||||
try:
|
||||
grants = graph_get(token, f"https://graph.microsoft.com/v1.0/servicePrincipals/{sp_id}/appRoleAssignments")
|
||||
roles = grants.get('value', [])
|
||||
print(f"[INFO] {len(roles)} app role assignments")
|
||||
# Get unique resource names
|
||||
resources = set()
|
||||
for r in roles:
|
||||
resources.add(r.get('resourceDisplayName', '?'))
|
||||
for res in sorted(resources):
|
||||
count = sum(1 for r in roles if r.get('resourceDisplayName') == res)
|
||||
print(f" {res}: {count} permissions")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"[INFO] Cannot read appRoleAssignments: HTTP {e.code}")
|
||||
|
||||
# Step 4: Find Exchange Admin role
|
||||
print("\n[STEP 4] Finding Exchange Administrator role...")
|
||||
try:
|
||||
roles_result = graph_get(token, "https://graph.microsoft.com/v1.0/directoryRoles?$select=id,displayName,roleTemplateId")
|
||||
exo_role = None
|
||||
for role in roles_result.get('value', []):
|
||||
if role.get('displayName') == 'Exchange Administrator':
|
||||
exo_role = role
|
||||
break
|
||||
|
||||
if not exo_role:
|
||||
print("[INFO] Exchange Admin not activated, activating from template...")
|
||||
try:
|
||||
activate = graph_post(token, "https://graph.microsoft.com/v1.0/directoryRoles",
|
||||
{"roleTemplateId": "29232cdf-9323-42fd-ade2-1d097af3e4de"})
|
||||
exo_role = activate
|
||||
print(f"[OK] Activated: {activate.get('id')}")
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode()
|
||||
print(f"[ERROR] Activation failed: HTTP {e.code} - {body[:200]}")
|
||||
sys.exit(1)
|
||||
|
||||
exo_role_id = exo_role['id']
|
||||
print(f"[OK] Exchange Admin Role ID: {exo_role_id}")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"[ERROR] Cannot list directory roles: HTTP {e.code}")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 5: Assign Exchange Admin to Claude SP
|
||||
print("\n[STEP 5] Assigning Exchange Admin role to Claude SP...")
|
||||
try:
|
||||
assign_body = {"@odata.id": f"https://graph.microsoft.com/v1.0/servicePrincipals/{sp_id}"}
|
||||
graph_post(token, f"https://graph.microsoft.com/v1.0/directoryRoles/{exo_role_id}/members/$ref", assign_body)
|
||||
print("[OK] Exchange Administrator assigned!")
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode()
|
||||
if 'already exist' in body.lower():
|
||||
print("[OK] Exchange Administrator already assigned")
|
||||
else:
|
||||
print(f"[ERROR] HTTP {e.code}: {body[:300]}")
|
||||
|
||||
# Step 6: Test Exchange REST API
|
||||
print("\n[STEP 6] Testing Exchange REST API...")
|
||||
exo_token = get_token(TENANT_ID, CLAUDE_APP, CLAUDE_SECRET, "https://outlook.office365.com/.default")
|
||||
invoke_url = f"https://outlook.office365.com/adminapi/beta/{TENANT_ID}/InvokeCommand"
|
||||
headers = {'Authorization': f'Bearer {exo_token}', 'Content-Type': 'application/json'}
|
||||
|
||||
cmd = json.dumps({
|
||||
"CmdletInput": {
|
||||
"CmdletName": "Get-Mailbox",
|
||||
"Parameters": {"Identity": "barbara@bardach.net", "ResultSize": "1"}
|
||||
}
|
||||
}).encode()
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(invoke_url, data=cmd, headers=headers, method='POST')
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
result = json.loads(resp.read())
|
||||
if result.get('value'):
|
||||
mb = result['value'][0]
|
||||
print(f"[OK] Exchange access works - {mb.get('DisplayName', '?')}")
|
||||
else:
|
||||
print(f"[OK] Exchange responded: {json.dumps(result)[:200]}")
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode()
|
||||
print(f"[ERROR] Exchange REST: HTTP {e.code} - {body[:300]}")
|
||||
Reference in New Issue
Block a user