#!/usr/bin/env python3
"""
Valley Wide Plastering - BEC (Business Email Compromise) Investigation
Target: jrguerrero@valleywideplastering.com
Date: 2026-03-05
"""

import subprocess
import json
import sys
import os
from datetime import datetime, timedelta, timezone

# Configuration
TENANT_ID = "5c53ae9f-7071-4248-b834-8685b646450f"
CLIENT_ID = "fabb3421-8b34-484b-bc17-e46de9703418"
CLIENT_SECRET = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
GRAPH_BASE = "https://graph.microsoft.com/v1.0"
GRAPH_BETA = "https://graph.microsoft.com/beta"
TARGET_USER = "jrguerrero"
TARGET_DOMAIN = "valleywideplastering.com"
RESULTS_FILE = "D:/ClaudeTools/temp/vwp_bec_results.json"

results = {}

def get_token():
    """Get OAuth2 token via client credentials flow."""
    cmd = [
        "curl", "-s", "-X", "POST",
        f"https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token",
        "-H", "Content-Type: application/x-www-form-urlencoded",
        "-d", f"client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&grant_type=client_credentials"
    ]
    result = subprocess.run(cmd, capture_output=True, text=True)
    data = json.loads(result.stdout)
    if "access_token" not in data:
        print(f"[ERROR] Failed to get token: {json.dumps(data, indent=2)}")
        sys.exit(1)
    return data["access_token"]


def graph_get(token, url, label=""):
    """Make a GET request to Microsoft Graph API."""
    cmd = ["curl", "-s", "-G", "-H", f"Authorization: Bearer {token}"]
    # Split URL and query params to let curl handle encoding
    if "?" in url:
        base_url, query_string = url.split("?", 1)
        cmd.append(base_url)
        # Pass each query param via --data-urlencode
        for param in query_string.split("&"):
            if "=" in param:
                cmd.extend(["--data-urlencode", param])
            else:
                cmd.extend(["--data-urlencode", param])
    else:
        cmd.append(url)
    result = subprocess.run(cmd, capture_output=True, text=True)
    try:
        data = json.loads(result.stdout)
    except json.JSONDecodeError:
        data = {"error": "Failed to parse response", "raw": result.stdout[:500]}
    if "error" in data:
        err_msg = data['error'].get('message', data['error']) if isinstance(data['error'], dict) else data['error']
        print(f"  [WARNING] {label}: {err_msg}")
    return data


def graph_get_all_pages(token, url, label=""):
    """Get all pages of a paginated Graph API response."""
    all_values = []
    current_url = url
    page = 0
    while current_url:
        page += 1
        data = graph_get(token, current_url, f"{label} page {page}")
        if "value" in data:
            all_values.extend(data["value"])
        else:
            break
        current_url = data.get("@odata.nextLink")
    return all_values


def print_separator(title):
    print(f"\n{'='*70}")
    print(f"  {title}")
    print(f"{'='*70}")


def main():
    print("=" * 70)
    print("  VALLEY WIDE PLASTERING - BEC INVESTIGATION")
    print(f"  Date: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}")
    print("=" * 70)

    # Get token
    print("\n[*] Acquiring access token...")
    token = get_token()
    print("[OK] Token acquired successfully")

    # ===== STEP 1: LIST ALL USERS =====
    print_separator("STEP 1: ALL TENANT USERS")
    users_data = graph_get(token, f"{GRAPH_BASE}/users?$select=id,displayName,userPrincipalName,mail,accountEnabled,createdDateTime", "users")
    users = users_data.get("value", [])
    results["users"] = users

    jr_id = None
    jr_upn = None
    all_upns = []

    for u in users:
        upn = u.get("userPrincipalName", "")
        all_upns.append(upn)
        enabled = u.get("accountEnabled", False)
        created = u.get("createdDateTime", "N/A")
        status = "[ENABLED]" if enabled else "[DISABLED]"
        name = u.get("displayName", "N/A")
        uid = u.get("id", "N/A")
        print(f"  {status} {name} | {upn} | ID: {uid} | Created: {created}")

        if TARGET_USER.lower() in upn.lower():
            jr_id = uid
            jr_upn = upn
            print(f"  >>> TARGET USER FOUND: {jr_upn} (ID: {jr_id})")

    if not jr_id:
        print(f"\n[INFO] Exact match for '{TARGET_USER}' not found, searching by name...")
        # Search for JR Guerrero specifically
        for u in users:
            upn = u.get("userPrincipalName", "")
            name = u.get("displayName", "")
            # Match "JR Guerrero" by display name or j-r@ pattern
            if name.lower() == "jr guerrero" and TARGET_DOMAIN in upn.lower():
                jr_id = u["id"]
                jr_upn = upn
                print(f"  >>> TARGET USER FOUND: {jr_upn} (ID: {jr_id})")
                break
        # Fallback broader search
        if not jr_id:
            for u in users:
                upn = u.get("userPrincipalName", "")
                name = u.get("displayName", "")
                if ("j-r" in upn.lower() or "jr" in upn.lower()) and TARGET_DOMAIN in upn.lower():
                    jr_id = u["id"]
                    jr_upn = upn
                    print(f"  >>> TARGET USER FOUND: {jr_upn} (ID: {jr_id})")
                    break

    if not jr_id:
        print("[ERROR] Cannot proceed without target user. Exiting.")
        results["error"] = "Target user not found"
        with open(RESULTS_FILE, "w") as f:
            json.dump(results, f, indent=2)
        sys.exit(1)

    results["target_user"] = {"id": jr_id, "upn": jr_upn}

    # ===== STEP 2: SIGN-IN LOGS =====
    print_separator("STEP 2: SIGN-IN LOGS (Last 14 Days)")

    # Try v1.0 first, then beta
    signin_url = f"{GRAPH_BASE}/auditLogs/signIns?$filter=userPrincipalName eq '{jr_upn}'&$top=100&$orderby=createdDateTime desc"
    signin_data = graph_get(token, signin_url, "sign-ins v1.0")
    signins = signin_data.get("value", [])

    if not signins and "error" in signin_data:
        print("  [*] Trying beta endpoint...")
        signin_url = f"{GRAPH_BETA}/auditLogs/signIns?$filter=userPrincipalName eq '{jr_upn}'&$top=100&$orderby=createdDateTime desc"
        signin_data = graph_get(token, signin_url, "sign-ins beta")
        signins = signin_data.get("value", [])

    results["signins"] = signins

    if signins:
        ips_seen = {}
        locations_seen = {}
        failed_count = 0
        risky_count = 0
        legacy_auth = []
        timestamps_by_ip = {}

        for s in signins:
            ts = s.get("createdDateTime", "N/A")
            ip = s.get("ipAddress", "N/A")
            loc = s.get("location", {})
            city = loc.get("city", "Unknown")
            state = loc.get("state", "Unknown")
            country = loc.get("countryOrRegion", "Unknown")
            loc_str = f"{city}, {state}, {country}"
            status_code = s.get("status", {}).get("errorCode", 0)
            status_reason = s.get("status", {}).get("failureReason", "")
            risk_level = s.get("riskLevelDuringSignIn", "none")
            risk_state = s.get("riskState", "none")
            app = s.get("clientAppUsed", "N/A")
            resource = s.get("resourceDisplayName", "N/A")
            app_name = s.get("appDisplayName", "N/A")

            # Track IPs
            ips_seen[ip] = ips_seen.get(ip, 0) + 1
            locations_seen[loc_str] = locations_seen.get(loc_str, 0) + 1
            if ip not in timestamps_by_ip:
                timestamps_by_ip[ip] = []
            timestamps_by_ip[ip].append(ts)

            # Check for issues
            is_failed = status_code != 0
            is_risky = risk_level not in ("none", "low", None)
            is_legacy = app in ("IMAP4", "POP3", "SMTP", "Exchange ActiveSync", "Authenticated SMTP",
                               "Other clients", "Exchange Online PowerShell", "MAPI Over HTTP",
                               "Offline Address Book", "Outlook Anywhere (RPC over HTTP)",
                               "Exchange Web Services", "POP", "IMAP")

            if is_failed:
                failed_count += 1
            if is_risky:
                risky_count += 1
            if is_legacy:
                legacy_auth.append({"timestamp": ts, "protocol": app, "ip": ip})

            # Flag suspicious entries
            flags = []
            if is_failed:
                flags.append("FAILED")
            if is_risky:
                flags.append(f"RISKY:{risk_level}")
            if is_legacy:
                flags.append("LEGACY_AUTH")
            if country not in ("US", "Unknown", "", None):
                flags.append(f"FOREIGN:{country}")

            flag_str = f" [{'|'.join(flags)}]" if flags else ""
            marker = "[SUSPICIOUS]" if flags else "            "

            print(f"  {marker} {ts} | IP: {ip} | {loc_str} | App: {app_name} | Client: {app} | Status: {status_code}{flag_str}")

        # Summary
        print(f"\n  --- Sign-in Summary ---")
        print(f"  Total sign-ins: {len(signins)}")
        print(f"  Unique IPs: {len(ips_seen)}")
        print(f"  Failed sign-ins: {failed_count}")
        print(f"  Risky sign-ins: {risky_count}")
        print(f"  Legacy auth protocols: {len(legacy_auth)}")

        print(f"\n  --- IP Address Breakdown ---")
        for ip, count in sorted(ips_seen.items(), key=lambda x: x[1], reverse=True):
            print(f"    {ip}: {count} sign-ins")

        print(f"\n  --- Location Breakdown ---")
        for loc, count in sorted(locations_seen.items(), key=lambda x: x[1], reverse=True):
            print(f"    {loc}: {count} sign-ins")

        if legacy_auth:
            print(f"\n  [CRITICAL] Legacy Authentication Detected!")
            for la in legacy_auth:
                print(f"    {la['timestamp']} | Protocol: {la['protocol']} | IP: {la['ip']}")

        # Impossible travel check
        if len(ips_seen) > 1:
            print(f"\n  --- Impossible Travel Check ---")
            print(f"  Multiple IPs detected. Review timestamps above for geographic anomalies.")

        results["signin_summary"] = {
            "total": len(signins),
            "unique_ips": len(ips_seen),
            "failed": failed_count,
            "risky": risky_count,
            "legacy_auth_count": len(legacy_auth),
            "ips": ips_seen,
            "locations": locations_seen,
            "legacy_auth_details": legacy_auth
        }
    else:
        print("  No sign-in logs found (tenant may not have Azure AD P1/P2)")

    # ===== STEP 3: RECENT SENT MAIL =====
    print_separator("STEP 3: RECENT SENT MAIL (Last 14 Days)")

    mail_url = f"{GRAPH_BASE}/users/{jr_id}/mailFolders/SentItems/messages?$top=100&$orderby=sentDateTime desc&$select=subject,toRecipients,ccRecipients,sentDateTime,bodyPreview,from,sender,hasAttachments"
    mail_data = graph_get(token, mail_url, "sent mail")
    sent_msgs = mail_data.get("value", [])
    results["sent_mail"] = sent_msgs

    suspicious_subjects = ["invoice", "payment", "wire", "transfer", "docusign", "urgent",
                          "overdue", "past due", "bank", "account", "verify", "confirm",
                          "update", "action required", "immediately", "asap", "w-2", "w2",
                          "tax", "direct deposit", "ach", "routing", "gift card"]

    if sent_msgs:
        sus_mail_count = 0
        external_recipients = set()

        for msg in sent_msgs:
            subject = msg.get("subject", "(No Subject)")
            sent_dt = msg.get("sentDateTime", "N/A")
            has_attach = msg.get("hasAttachments", False)
            body_preview = msg.get("bodyPreview", "")[:150]
            to_list = msg.get("toRecipients", [])
            cc_list = msg.get("ccRecipients", [])

            # Check recipients
            to_addrs = []
            for r in to_list:
                addr = r.get("emailAddress", {}).get("address", "N/A")
                to_addrs.append(addr)
                if TARGET_DOMAIN not in addr.lower():
                    external_recipients.add(addr)
            for r in cc_list:
                addr = r.get("emailAddress", {}).get("address", "N/A")
                if TARGET_DOMAIN not in addr.lower():
                    external_recipients.add(addr)

            # Check for suspicious subjects
            is_suspicious = any(kw in subject.lower() for kw in suspicious_subjects)
            if is_suspicious:
                sus_mail_count += 1

            attach_str = " [HAS ATTACHMENTS]" if has_attach else ""
            marker = "[SUSPICIOUS]" if is_suspicious else "            "

            print(f"  {marker} {sent_dt} | To: {', '.join(to_addrs[:3])} | Subject: {subject}{attach_str}")
            if is_suspicious:
                print(f"               Preview: {body_preview}")

        print(f"\n  --- Sent Mail Summary ---")
        print(f"  Total sent messages: {len(sent_msgs)}")
        print(f"  Suspicious subjects: {sus_mail_count}")
        print(f"  External recipients: {len(external_recipients)}")
        if external_recipients:
            print(f"  External recipient list:")
            for addr in sorted(external_recipients):
                print(f"    - {addr}")

        results["sent_mail_summary"] = {
            "total": len(sent_msgs),
            "suspicious_count": sus_mail_count,
            "external_recipients": list(external_recipients)
        }
    else:
        print("  No sent mail found or access denied")

    # ===== STEP 4: INBOX RULES =====
    print_separator("STEP 4: INBOX RULES (CRITICAL CHECK)")

    rules_url = f"{GRAPH_BASE}/users/{jr_id}/mailFolders/inbox/messageRules"
    rules_data = graph_get(token, rules_url, "inbox rules")
    rules = rules_data.get("value", [])
    results["inbox_rules"] = rules

    if rules:
        for rule in rules:
            rule_name = rule.get("displayName", "Unnamed Rule")
            is_enabled = rule.get("isEnabled", False)
            actions = rule.get("actions", {})
            conditions = rule.get("conditions", {})

            # Check for malicious rule patterns
            flags = []
            forward_to = actions.get("forwardTo", [])
            forward_as = actions.get("forwardAsAttachmentTo", [])
            redirect_to = actions.get("redirectTo", [])
            delete = actions.get("delete", False)
            move_to = actions.get("moveToFolder", "")
            mark_read = actions.get("markAsRead", False)
            perm_delete = actions.get("permanentDelete", False)

            if forward_to:
                fwd_addrs = [f.get("emailAddress", {}).get("address", "N/A") for f in forward_to]
                flags.append(f"FORWARDS TO: {', '.join(fwd_addrs)}")
            if forward_as:
                fwd_addrs = [f.get("emailAddress", {}).get("address", "N/A") for f in forward_as]
                flags.append(f"FORWARDS AS ATTACHMENT TO: {', '.join(fwd_addrs)}")
            if redirect_to:
                redir_addrs = [r.get("emailAddress", {}).get("address", "N/A") for r in redirect_to]
                flags.append(f"REDIRECTS TO: {', '.join(redir_addrs)}")
            if delete:
                flags.append("DELETES MESSAGES")
            if perm_delete:
                flags.append("PERMANENTLY DELETES MESSAGES")
            if mark_read:
                flags.append("MARKS AS READ")

            status = "[ENABLED]" if is_enabled else "[DISABLED]"
            marker = "[CRITICAL]" if (forward_to or redirect_to or delete or perm_delete) else "          "

            print(f"  {marker} {status} Rule: '{rule_name}'")
            if flags:
                for f in flags:
                    print(f"    >>> {f}")
            if conditions:
                print(f"    Conditions: {json.dumps(conditions, indent=6)}")
            print(f"    Full actions: {json.dumps(actions, indent=6)}")
    else:
        print("  [OK] No inbox rules found")

    # ===== STEP 5: MAILBOX SETTINGS =====
    print_separator("STEP 5: MAILBOX SETTINGS (Forwarding & Auto-Reply)")

    mailbox_url = f"{GRAPH_BASE}/users/{jr_id}/mailboxSettings"
    mailbox_data = graph_get(token, mailbox_url, "mailbox settings")
    results["mailbox_settings"] = mailbox_data

    if "error" not in mailbox_data:
        auto_reply = mailbox_data.get("automaticRepliesSetting", {})
        ar_status = auto_reply.get("status", "disabled")
        ar_external = auto_reply.get("externalReplyMessage", "")
        ar_internal = auto_reply.get("internalReplyMessage", "")

        print(f"  Auto-Reply Status: {ar_status}")
        if ar_status != "disabled":
            print(f"  [SUSPICIOUS] Auto-replies are ENABLED!")
            print(f"  External message: {ar_external[:200]}")
            print(f"  Internal message: {ar_internal[:200]}")
        else:
            print(f"  [OK] Auto-replies are disabled")

        # Check other settings
        print(f"  Language: {mailbox_data.get('language', {}).get('locale', 'N/A')}")
        print(f"  Timezone: {mailbox_data.get('timeZone', 'N/A')}")
        print(f"  Date format: {mailbox_data.get('dateFormat', 'N/A')}")
    else:
        print("  Could not retrieve mailbox settings")

    # Also check forwarding via Exchange settings
    print("\n  Checking SMTP forwarding...")
    fwd_url = f"{GRAPH_BASE}/users/{jr_id}?$select=mail,otherMails,proxyAddresses"
    fwd_data = graph_get(token, fwd_url, "forwarding check")
    if "error" not in fwd_data:
        proxy = fwd_data.get("proxyAddresses", [])
        other = fwd_data.get("otherMails", [])
        print(f"  Proxy addresses: {proxy}")
        print(f"  Other emails: {other}")
    results["forwarding_check"] = fwd_data

    # ===== STEP 6: AUTHENTICATION METHODS =====
    print_separator("STEP 6: AUTHENTICATION METHODS")

    auth_url = f"{GRAPH_BASE}/users/{jr_id}/authentication/methods"
    auth_data = graph_get(token, auth_url, "auth methods")
    auth_methods = auth_data.get("value", [])
    results["auth_methods"] = auth_methods

    if auth_methods:
        for m in auth_methods:
            method_type = m.get("@odata.type", "Unknown")
            method_id = m.get("id", "N/A")

            # Clean up type name
            clean_type = method_type.replace("#microsoft.graph.", "")

            detail = ""
            if "phone" in method_type.lower():
                detail = f" | Phone: {m.get('phoneNumber', 'N/A')} ({m.get('phoneType', 'N/A')})"
            elif "email" in method_type.lower():
                detail = f" | Email: {m.get('emailAddress', 'N/A')}"
            elif "fido2" in method_type.lower():
                detail = f" | Model: {m.get('model', 'N/A')} | Created: {m.get('createdDateTime', 'N/A')}"
            elif "microsoftAuthenticator" in method_type:
                detail = f" | Device: {m.get('displayName', 'N/A')} | Created: {m.get('createdDateTime', 'N/A')}"
            elif "softwareOath" in method_type:
                detail = f" | Created: {m.get('createdDateTime', 'N/A')}"

            print(f"  [{clean_type}] ID: {method_id}{detail}")
    else:
        print("  No auth methods returned (may need different permissions)")

    # ===== STEP 7: OAUTH PERMISSION GRANTS =====
    print_separator("STEP 7: OAUTH PERMISSION GRANTS & THIRD-PARTY APPS")

    oauth_url = f"{GRAPH_BASE}/users/{jr_id}/oauth2PermissionGrants"
    oauth_data = graph_get(token, oauth_url, "oauth grants")
    oauth_grants = oauth_data.get("value", [])
    results["oauth_grants"] = oauth_grants

    if oauth_grants:
        print(f"  Found {len(oauth_grants)} OAuth permission grant(s):")
        for grant in oauth_grants:
            client_id = grant.get("clientId", "N/A")
            scope = grant.get("scope", "N/A")
            consent = grant.get("consentType", "N/A")
            start = grant.get("startTime", "N/A")

            # Flag mail-related scopes
            mail_scopes = ["Mail", "IMAP", "POP", "SMTP", "EWS"]
            has_mail = any(ms.lower() in scope.lower() for ms in mail_scopes)
            marker = "[SUSPICIOUS]" if has_mail else "            "

            print(f"  {marker} ClientID: {client_id}")
            print(f"             Scope: {scope}")
            print(f"             Consent: {consent} | Start: {start}")
    else:
        print("  [OK] No OAuth permission grants found for user")

    # Check third-party service principals
    print("\n  Checking third-party service principals...")
    sp_url = f"{GRAPH_BASE}/servicePrincipals?$filter=appOwnerOrganizationId ne {TENANT_ID}&$select=displayName,appId,appOwnerOrganizationId&$top=50"
    sp_data = graph_get(token, sp_url, "service principals")
    sps = sp_data.get("value", [])
    results["third_party_apps"] = sps

    if sps:
        print(f"  Third-party apps in tenant ({len(sps)}):")
        # Known Microsoft org IDs
        ms_org_ids = [
            "f8cdef31-a31e-4b4a-93e4-5f571e91255a",  # Microsoft
            "47df5bb7-e6bc-4256-afb0-dd8c8e3c1ce8",  # Microsoft
            "72f988bf-86f1-41af-91ab-2d7cd011db47",  # Microsoft Corp
        ]
        for sp in sps:
            org_id = sp.get("appOwnerOrganizationId", "")
            is_ms = org_id in ms_org_ids
            if not is_ms:
                print(f"  [INFO] {sp.get('displayName', 'N/A')} | AppID: {sp.get('appId', 'N/A')} | OrgID: {org_id}")
    else:
        print("  No third-party service principals found or filter not supported")

    # ===== STEP 8: AUDIT LOGS =====
    print_separator("STEP 8: DIRECTORY AUDIT LOGS (Recent Changes)")

    # Try different filter approaches
    audit_url = f"{GRAPH_BASE}/auditLogs/directoryAudits?$filter=targetResources/any(t:t/userPrincipalName eq '{jr_upn}')&$top=50&$orderby=activityDateTime desc"
    audit_data = graph_get(token, audit_url, "audit logs")
    audit_entries = audit_data.get("value", [])

    if not audit_entries and "error" in audit_data:
        # Try beta
        print("  [*] Trying beta endpoint...")
        audit_url = f"{GRAPH_BETA}/auditLogs/directoryAudits?$filter=targetResources/any(t:t/userPrincipalName eq '{jr_upn}')&$top=50&$orderby=activityDateTime desc"
        audit_data = graph_get(token, audit_url, "audit logs beta")
        audit_entries = audit_data.get("value", [])

    if not audit_entries and "error" in audit_data:
        # Try without filter
        print("  [*] Trying unfiltered audit logs (will filter manually)...")
        audit_url = f"{GRAPH_BASE}/auditLogs/directoryAudits?$top=100&$orderby=activityDateTime desc"
        audit_data = graph_get(token, audit_url, "audit logs unfiltered")
        all_audits = audit_data.get("value", [])
        # Filter manually
        for a in all_audits:
            targets = a.get("targetResources", [])
            for t in targets:
                if jr_upn.lower() in t.get("userPrincipalName", "").lower() or jr_id in t.get("id", ""):
                    audit_entries.append(a)
                    break

    results["audit_logs"] = audit_entries

    if audit_entries:
        # Key activities to watch for
        critical_activities = [
            "Reset password", "Change password", "Reset user password",
            "Update user", "Add member to role", "Add app role assignment",
            "Consent to application", "Add OAuth2PermissionGrant",
            "Update authentication method", "Register security info",
            "Add registered owner", "Add service principal",
            "Set force change password", "Disable account"
        ]

        for a in audit_entries:
            activity = a.get("activityDisplayName", "N/A")
            ts = a.get("activityDateTime", "N/A")
            result_status = a.get("result", "N/A")
            initiated_by = a.get("initiatedBy", {})
            user_info = initiated_by.get("user") or {}
            app_info = initiated_by.get("app") or {}
            actor_upn = user_info.get("userPrincipalName", "N/A")
            actor_app = app_info.get("displayName", "")

            is_critical = any(ca.lower() in activity.lower() for ca in critical_activities)
            marker = "[CRITICAL]" if is_critical else "          "

            actor = actor_upn if actor_upn != "N/A" else actor_app
            print(f"  {marker} {ts} | {activity} | Result: {result_status} | Actor: {actor}")

            if is_critical:
                targets = a.get("targetResources", [])
                for t in targets:
                    modified = t.get("modifiedProperties", [])
                    for mp in modified:
                        print(f"    Changed: {mp.get('displayName', 'N/A')}: {str(mp.get('oldValue', ''))[:80]} -> {str(mp.get('newValue', ''))[:80]}")
    else:
        print("  No audit log entries found for target user")

    # ===== STEP 9: CHECK ALL OTHER USERS FOR RISKY SIGN-INS =====
    print_separator("STEP 9: LATERAL MOVEMENT CHECK (All Users Risky Sign-ins)")

    other_users = [u for u in users if u.get("id") != jr_id and u.get("accountEnabled", False)]
    results["lateral_movement"] = {}

    for u in other_users:
        upn = u.get("userPrincipalName", "")
        name = u.get("displayName", "")
        if not upn:
            continue

        # Check for risky sign-ins
        risk_url = f"{GRAPH_BASE}/auditLogs/signIns?$filter=userPrincipalName eq '{upn}'&$top=10&$orderby=createdDateTime desc"
        risk_data = graph_get(token, risk_url, f"risk check {upn}")
        risk_signins = risk_data.get("value", [])

        user_risks = []
        for s in risk_signins:
            risk_level = s.get("riskLevelDuringSignIn", "none")
            risk_state = s.get("riskState", "none")
            status_code = s.get("status", {}).get("errorCode", 0)
            ip = s.get("ipAddress", "N/A")
            loc = s.get("location", {})
            country = loc.get("countryOrRegion", "")
            ts = s.get("createdDateTime", "")
            app = s.get("clientAppUsed", "")

            # Flag anything risky
            is_risky = risk_level not in ("none", "low", None, "")
            is_foreign = country not in ("US", "Unknown", "", None)
            is_legacy = app in ("IMAP4", "POP3", "SMTP", "Authenticated SMTP", "Other clients")
            is_failed = status_code != 0

            if is_risky or is_foreign or is_legacy:
                user_risks.append({
                    "timestamp": ts, "ip": ip, "country": country,
                    "risk_level": risk_level, "protocol": app,
                    "failed": is_failed
                })

        if user_risks:
            print(f"\n  [SUSPICIOUS] {name} ({upn}):")
            for r in user_risks:
                print(f"    {r['timestamp']} | IP: {r['ip']} | Country: {r['country']} | Risk: {r['risk_level']} | Protocol: {r['protocol']}")
            results["lateral_movement"][upn] = user_risks
        else:
            print(f"  [OK] {name} ({upn}): No risky sign-ins detected")

    # ===== SAVE RESULTS =====
    print_separator("SAVING RESULTS")
    with open(RESULTS_FILE, "w") as f:
        json.dump(results, f, indent=2, default=str)
    print(f"  Results saved to: {RESULTS_FILE}")

    # ===== INCIDENT REPORT SUMMARY =====
    print_separator("INCIDENT REPORT SUMMARY")
    print(f"""
  Target: {jr_upn} (ID: {jr_id})
  Investigation Date: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}
  Tenant: Valley Wide Plastering ({TENANT_ID})
  Total Users in Tenant: {len(users)}

  KEY FINDINGS:
  =============
""")

    # Summarize findings
    findings = []

    # Sign-in findings
    signin_summary = results.get("signin_summary", {})
    if signin_summary:
        if signin_summary.get("failed", 0) > 10:
            findings.append(f"[CRITICAL] {signin_summary['failed']} failed sign-ins detected - possible brute force")
        if signin_summary.get("risky", 0) > 0:
            findings.append(f"[CRITICAL] {signin_summary['risky']} risky sign-ins flagged by Microsoft")
        if signin_summary.get("legacy_auth_count", 0) > 0:
            findings.append(f"[SUSPICIOUS] {signin_summary['legacy_auth_count']} legacy auth protocol usage detected")
        if signin_summary.get("unique_ips", 0) > 5:
            findings.append(f"[SUSPICIOUS] {signin_summary['unique_ips']} unique IP addresses used")

    # Mail findings
    mail_summary = results.get("sent_mail_summary", {})
    if mail_summary:
        if mail_summary.get("suspicious_count", 0) > 0:
            findings.append(f"[SUSPICIOUS] {mail_summary['suspicious_count']} emails with suspicious subjects")
        if len(mail_summary.get("external_recipients", [])) > 10:
            findings.append(f"[SUSPICIOUS] {len(mail_summary['external_recipients'])} external recipients in sent mail")

    # Inbox rules
    if rules:
        for rule in rules:
            actions = rule.get("actions", {})
            if actions.get("forwardTo") or actions.get("redirectTo") or actions.get("forwardAsAttachmentTo"):
                findings.append(f"[CRITICAL] Inbox rule '{rule.get('displayName', 'N/A')}' forwards/redirects mail externally")
            if actions.get("delete") or actions.get("permanentDelete"):
                findings.append(f"[CRITICAL] Inbox rule '{rule.get('displayName', 'N/A')}' deletes incoming mail")

    # Auto-reply
    if results.get("mailbox_settings", {}).get("automaticRepliesSetting", {}).get("status", "disabled") != "disabled":
        findings.append("[SUSPICIOUS] Auto-replies are enabled - check for phishing content")

    # OAuth
    if oauth_grants:
        findings.append(f"[INFO] {len(oauth_grants)} OAuth grants found - review for suspicious app access")

    # Lateral movement
    lateral = results.get("lateral_movement", {})
    if lateral:
        findings.append(f"[SUSPICIOUS] {len(lateral)} other users show suspicious sign-in activity")

    if findings:
        for f in findings:
            print(f"  {f}")
    else:
        print("  No critical findings detected. Review detailed output above for context.")

    print(f"""
  RECOMMENDED ACTIONS:
  ====================
  1. Reset JR Guerrero's password immediately
  2. Revoke all active sessions (Entra ID > Users > Revoke sessions)
  3. Enable MFA if not already enabled
  4. Remove any suspicious inbox rules
  5. Disable any unauthorized OAuth app grants
  6. Block legacy authentication via Conditional Access
  7. Review sent items for any phishing emails sent from this account
  8. Notify recipients of any suspicious emails
  9. Check for data exfiltration via OneDrive/SharePoint
  10. Monitor account for next 30 days

  Investigation script: D:/ClaudeTools/temp/vwp_bec_investigation.py
  Raw results: {RESULTS_FILE}
""")


if __name__ == "__main__":
    main()
