#!/usr/bin/env python3
"""OAuth 2.0 authorization flow security audit agent."""

import json
import sys
import argparse
from datetime import datetime

try:
    import requests
except ImportError:
    print("Install: pip install requests")
    sys.exit(1)


def discover_oauth_endpoints(issuer_url):
    """Discover OAuth 2.0 / OIDC endpoints from well-known configuration."""
    discovery_url = f"{issuer_url.rstrip('/')}/.well-known/openid-configuration"
    try:
        resp = requests.get(discovery_url, timeout=10)
        resp.raise_for_status()
        config = resp.json()
        return {
            "issuer": config.get("issuer", ""),
            "authorization_endpoint": config.get("authorization_endpoint", ""),
            "token_endpoint": config.get("token_endpoint", ""),
            "userinfo_endpoint": config.get("userinfo_endpoint", ""),
            "jwks_uri": config.get("jwks_uri", ""),
            "supported_grant_types": config.get("grant_types_supported", []),
            "supported_scopes": config.get("scopes_supported", []),
            "supported_response_types": config.get("response_types_supported", []),
            "token_endpoint_auth_methods": config.get("token_endpoint_auth_methods_supported", []),
        }
    except Exception as e:
        return {"error": str(e)}


def audit_oauth_security(config):
    """Audit OAuth configuration for security issues."""
    findings = []
    if "implicit" in config.get("supported_grant_types", []):
        findings.append({
            "issue": "Implicit grant type supported",
            "severity": "HIGH",
            "recommendation": "Disable implicit flow; use authorization code + PKCE",
        })
    if "password" in config.get("supported_grant_types", []):
        findings.append({
            "issue": "Resource owner password grant supported",
            "severity": "MEDIUM",
            "recommendation": "Disable ROPC grant; use authorization code flow",
        })
    auth_methods = config.get("token_endpoint_auth_methods", [])
    if "none" in auth_methods:
        findings.append({
            "issue": "Token endpoint allows unauthenticated clients",
            "severity": "MEDIUM",
            "recommendation": "Require client_secret_basic or private_key_jwt",
        })
    if "code" in config.get("supported_response_types", []):
        if "code id_token" not in config.get("supported_response_types", []):
            findings.append({
                "issue": "Authorization code flow available",
                "severity": "INFO",
                "note": "Ensure PKCE is enforced for public clients",
            })
    return findings


def test_token_endpoint(token_url, client_id, client_secret, grant_type="client_credentials"):
    """Test token endpoint with client credentials."""
    try:
        resp = requests.post(token_url, data={
            "grant_type": grant_type,
            "client_id": client_id,
            "client_secret": client_secret,
        }, timeout=10)
        if resp.status_code == 200:
            token_data = resp.json()
            return {
                "status": "success",
                "token_type": token_data.get("token_type", ""),
                "expires_in": token_data.get("expires_in", 0),
                "scope": token_data.get("scope", ""),
            }
        return {"status": "failed", "code": resp.status_code, "body": resp.text[:200]}
    except Exception as e:
        return {"status": "error", "message": str(e)}


def run_audit(issuer_url, client_id=None, client_secret=None):
    """Execute OAuth 2.0 security audit."""
    print(f"\n{'='*60}")
    print(f"  OAUTH 2.0 AUTHORIZATION FLOW AUDIT")
    print(f"  Issuer: {issuer_url}")
    print(f"  Generated: {datetime.utcnow().isoformat()} UTC")
    print(f"{'='*60}\n")

    config = discover_oauth_endpoints(issuer_url)
    if "error" in config:
        print(f"  Error: {config['error']}")
        return config

    print(f"--- DISCOVERED ENDPOINTS ---")
    print(f"  Authorization: {config.get('authorization_endpoint', 'N/A')}")
    print(f"  Token: {config.get('token_endpoint', 'N/A')}")
    print(f"  JWKS: {config.get('jwks_uri', 'N/A')}")
    print(f"  Grant types: {config.get('supported_grant_types', [])}")

    findings = audit_oauth_security(config)
    print(f"\n--- SECURITY FINDINGS ({len(findings)}) ---")
    for f in findings:
        print(f"  [{f['severity']}] {f['issue']}")

    token_test = {}
    if client_id and client_secret and config.get("token_endpoint"):
        token_test = test_token_endpoint(config["token_endpoint"], client_id, client_secret)
        print(f"\n--- TOKEN ENDPOINT TEST ---")
        print(f"  Status: {token_test.get('status', 'N/A')}")

    return {"config": config, "findings": findings, "token_test": token_test}


def main():
    parser = argparse.ArgumentParser(description="OAuth 2.0 Audit Agent")
    parser.add_argument("--issuer", required=True, help="OAuth issuer URL")
    parser.add_argument("--client-id", help="Client ID for token test")
    parser.add_argument("--client-secret", help="Client secret for token test")
    parser.add_argument("--output", help="Save report to JSON file")
    args = parser.parse_args()

    report = run_audit(args.issuer, args.client_id, args.client_secret)
    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2, default=str)
        print(f"\n[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
