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:
199
temp/vwp_send_notification.py
Normal file
199
temp/vwp_send_notification.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
VWP BEC Incident - Send phishing notification to all affected recipients.
|
||||
Sends from JR's account via Microsoft Graph API sendMail endpoint.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
# === Configuration ===
|
||||
TENANT_ID = "5c53ae9f-7071-4248-b834-8685b646450f"
|
||||
APP_ID = "fabb3421-8b34-484b-bc17-e46de9703418"
|
||||
APP_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
|
||||
JR_USER_ID = "0af923d0-48c5-4cc1-8553-c60625802815"
|
||||
JR_EMAIL = "j-r@valleywideplastering.com"
|
||||
|
||||
RECIPIENTS_FILE = r"D:\ClaudeTools\temp\vwp_exchange_recipients.json"
|
||||
|
||||
# Addresses to exclude (lowercase for comparison)
|
||||
EXCLUDE_EXACT = {
|
||||
"j-r@valleywideplastering.com",
|
||||
"jr@valleywideplastering.com",
|
||||
"jr@casarica.net",
|
||||
"jrsuperwall@gmail.com",
|
||||
"kathya@azappliancehome.com.com", # malformed double .com
|
||||
}
|
||||
|
||||
EXCLUDE_DOMAINS = ["bidmail.com", "onmicrosoft.com"]
|
||||
|
||||
|
||||
def get_access_token():
|
||||
"""Acquire OAuth2 token via client credentials flow."""
|
||||
token_url = f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token"
|
||||
result = subprocess.run(
|
||||
[
|
||||
"curl", "-s", "-X", "POST", token_url,
|
||||
"-H", "Content-Type: application/x-www-form-urlencoded",
|
||||
"-d", f"client_id={APP_ID}&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret={APP_SECRET}&grant_type=client_credentials",
|
||||
],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
resp = json.loads(result.stdout)
|
||||
if "access_token" not in resp:
|
||||
print("[ERROR] Failed to get access token:")
|
||||
print(json.dumps(resp, indent=2))
|
||||
sys.exit(1)
|
||||
print("[OK] Access token acquired")
|
||||
return resp["access_token"]
|
||||
|
||||
|
||||
def load_recipients():
|
||||
"""Load and filter recipients from the JSON file."""
|
||||
with open(RECIPIENTS_FILE, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
raw = data["all_unique_recipients"]
|
||||
print(f"[INFO] Raw recipient count: {len(raw)}")
|
||||
|
||||
# Strip surrounding single quotes and whitespace
|
||||
cleaned = [addr.strip().strip("'").strip() for addr in raw]
|
||||
|
||||
# Filter and deduplicate (case-insensitive)
|
||||
seen = set()
|
||||
filtered = []
|
||||
for addr in cleaned:
|
||||
lower = addr.lower()
|
||||
|
||||
# Skip excluded exact addresses
|
||||
if lower in EXCLUDE_EXACT:
|
||||
continue
|
||||
|
||||
# Skip excluded domain patterns
|
||||
if any(domain in lower for domain in EXCLUDE_DOMAINS):
|
||||
continue
|
||||
|
||||
# Deduplicate
|
||||
if lower not in seen:
|
||||
seen.add(lower)
|
||||
filtered.append(addr)
|
||||
|
||||
print(f"[INFO] After filtering and dedup: {len(filtered)} BCC recipients")
|
||||
return filtered
|
||||
|
||||
|
||||
def send_notification(token, bcc_recipients):
|
||||
"""Send the notification email via Graph API sendMail endpoint."""
|
||||
subject = 'Security Notice - Do Not Open Box.com File "Valley Wide Plastering, INC......pdf"'
|
||||
|
||||
body_html = (
|
||||
'<p>You recently received a file sharing invitation from my Box.com account '
|
||||
'for a file named "Valley Wide Plastering, INC......pdf".</p>'
|
||||
'\n\n'
|
||||
'<p>This file was <strong>NOT</strong> sent by me. My email account was temporarily '
|
||||
'compromised, and the attacker used it to distribute this malicious link through '
|
||||
'Box.com. The issue has been resolved and my account has been secured.</p>'
|
||||
'\n\n'
|
||||
'<p><strong>Please take the following steps:</strong></p>'
|
||||
'\n'
|
||||
'<ol>'
|
||||
'<li>Do NOT open or click the Box.com link if you haven\'t already</li>'
|
||||
'<li>If you DID click the link or entered any credentials on the page, please '
|
||||
'change your password immediately and notify your IT department</li>'
|
||||
'<li>Delete the Box.com sharing notification email from your inbox</li>'
|
||||
'<li>If you created a Box.com account as a result of this invitation, consider removing it</li>'
|
||||
'</ol>'
|
||||
'\n\n'
|
||||
'<p>We apologize for the inconvenience. If you have any questions or concerns, '
|
||||
'please contact our office directly.</p>'
|
||||
'\n\n'
|
||||
'<p>JR Guerrero<br>Valley Wide Plastering, Inc.</p>'
|
||||
)
|
||||
|
||||
payload = {
|
||||
"message": {
|
||||
"subject": subject,
|
||||
"body": {
|
||||
"contentType": "HTML",
|
||||
"content": body_html
|
||||
},
|
||||
"toRecipients": [
|
||||
{"emailAddress": {"address": JR_EMAIL}}
|
||||
],
|
||||
"bccRecipients": [
|
||||
{"emailAddress": {"address": addr}} for addr in bcc_recipients
|
||||
]
|
||||
},
|
||||
"saveToSentItems": True
|
||||
}
|
||||
|
||||
# Write payload to temp file to avoid command-line length limits
|
||||
tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8")
|
||||
json.dump(payload, tmp, ensure_ascii=False)
|
||||
tmp.close()
|
||||
|
||||
send_url = f"https://graph.microsoft.com/v1.0/users/{JR_USER_ID}/sendMail"
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
"curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
|
||||
"-X", "POST", send_url,
|
||||
"-H", f"Authorization: Bearer {token}",
|
||||
"-H", "Content-Type: application/json",
|
||||
"-d", f"@{tmp.name}",
|
||||
],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
status_code = result.stdout.strip()
|
||||
print(f"[INFO] HTTP status code: {status_code}")
|
||||
|
||||
if status_code == "202":
|
||||
print("[SUCCESS] Notification email sent successfully!")
|
||||
else:
|
||||
print(f"[ERROR] Unexpected status code: {status_code}")
|
||||
# Re-run to capture response body for debugging
|
||||
result2 = subprocess.run(
|
||||
[
|
||||
"curl", "-s",
|
||||
"-X", "POST", send_url,
|
||||
"-H", f"Authorization: Bearer {token}",
|
||||
"-H", "Content-Type: application/json",
|
||||
"-d", f"@{tmp.name}",
|
||||
],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
print(result2.stdout[:2000])
|
||||
finally:
|
||||
os.unlink(tmp.name)
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("VWP BEC Incident - Phishing Notification Sender")
|
||||
print("=" * 60)
|
||||
|
||||
# Step 1: Load and filter recipients
|
||||
bcc_recipients = load_recipients()
|
||||
|
||||
# Print all recipients for verification
|
||||
print("\n[INFO] BCC recipient list:")
|
||||
for i, addr in enumerate(bcc_recipients, 1):
|
||||
print(f" {i:3d}. {addr}")
|
||||
print(f"\n[INFO] Total BCC recipients: {len(bcc_recipients)}")
|
||||
print(f"[INFO] To recipient: {JR_EMAIL}")
|
||||
|
||||
# Step 2: Get access token
|
||||
token = get_access_token()
|
||||
|
||||
# Step 3: Send the email
|
||||
print(f"\n[INFO] Sending notification email...")
|
||||
send_notification(token, bcc_recipients)
|
||||
|
||||
print("\n[OK] Done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user