#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
#     "curl-cffi>=0.15.0",
# ]
# ///
from toadex.bug_bank_detector import Target, run

DETECTOR_ID = "guidewire-policycenter-default-su-gw"
DETECTOR_TITLE = "Guidewire PolicyCenter default su:gw superuser credential"
DETECTOR_SEVERITY = "high"
DETECTOR_DESCRIPTION = """Guidewire PolicyCenter development and test documentation uses
the built-in `su` account with the password `gw` for HTTP Basic authentication. A
deployment is critically misconfigured when that stock credential remains valid on an
Internet-reachable REST API: an unauthenticated attacker can authenticate as the active
Super User and receive every permission assigned to the `Superuser` role.

This detector is fingerprint-only because proving the misconfiguration requires trying
a credential. It identifies PolicyCenter from its public System Tools OpenAPI document.
For a permitted manual check, first request the read-only identity resource
`/rest/admin/v1/users/default_data:1?fields=username,active,displayName,roles` without
credentials and confirm denial. Repeat it with HTTP Basic `su:gw`. Treat the deployment
as affected only when the target returns its own stable identity data showing
`username=su`, `active=true`, and a `Superuser` role. Stop after identity and role; do not
enumerate records or invoke any state-changing link. Some on-premises deployments mount
the same API below `/pc/rest/`.

This is configuration-dependent rather than tied to one PolicyCenter release. The
source deployment exposed System Tools API 1.15.0, which is an API version and must not
be treated as the product version. Remediate by changing or disabling the built-in
account, removing Internet access to development APIs, and disabling Basic
authentication wherever it is not explicitly required.

Primary references:
- https://docs.guidewire.com/cloud/pc/202503/cloudapibf/cloudAPI/topics/101-Fund/03_authentication/c_basic-authentication.html
- https://docs.guidewire.com/cloud/pc/202503/cloudapibf/cloudAPI/topics/101-Fund/02-first-call/c_send-a-GET-request.html
"""


def _is_policycenter_system_tools(document: object) -> bool:
    if not isinstance(document, dict):
        return False
    info = document.get("info")
    components = document.get("components")
    paths = document.get("paths")
    if not isinstance(info, dict) or not isinstance(components, dict) or not isinstance(paths, dict):
        return False
    schemes = components.get("securitySchemes")
    if not isinstance(schemes, dict):
        return False
    basic = schemes.get("basicAuth")
    return (
        info.get("title") == "PolicyCenter System Tools API"
        and info.get("description") == "APIs for InsuranceSuite system tools"
        and isinstance(basic, dict)
        and basic.get("type") == "http"
        and basic.get("scheme") == "basic"
        and "/system-time" in paths
        and "/batch-processes" in paths
    )


async def fingerprint(target: Target) -> bool:
    """Identify Guidewire PolicyCenter from its distinctive System Tools schema."""
    paths = [
        "/rest/systemtools/v1/openapi.json",
        "/pc/rest/systemtools/v1/openapi.json",
    ]
    for path in paths:
        response = await target.get(path, timeout=6)
        if response.status_code != 200:
            continue
        try:
            document = response.json()
        except Exception:
            continue
        if _is_policycenter_system_tools(document):
            return True
    return False


if __name__ == "__main__":
    raise SystemExit(run(fingerprint=fingerprint))
