"""TS-AUTHZ-001: Missing Per-Tool Authorization for TypeScript/JavaScript.

Detects MCP tool handlers that execute operations without checking
authorization, permissions, or tokens.

OWASP Agentic: Each tool invocation should verify that the caller
is authorized to perform that specific action, not just that they
are authenticated at the transport level.
"""

from __future__ import annotations

import re

from . import TSRule
from ..models import Finding, Severity

# MCP server.tool() handler registration patterns
TOOL_HANDLER_PATTERN = re.compile(
    r'\bserver\s*\.\s*tool\s*\('
)

# Patterns that indicate authorization is being checked
PER_TOOL_AUTHZ_PATTERNS = re.compile(
    r'(?:authorize|checkPermission|hasPermission|verifyPermission|'
    r'isAuthorized|requirePermission|canExecute|canPerform|'
    r'checkRole|hasRole|verifyRole|requireRole|'
    r'auth\.check|authz\.check|rbac\.|acl\.|'
    r'permission\.check|permissions\.includes|'
    r'token\s*[&|=!]|auth_token|authToken|bearer|'
    r'ctx\.auth|context\.auth|session\.role|'
    r'assertRole|assertPermission|guardTool|'
    r'middleware.*auth|auth.*middleware)',
    re.IGNORECASE,
)

# Patterns for handler body extraction (the callback/function)
HANDLER_CALLBACK = re.compile(
    r'server\s*\.\s*tool\s*\(\s*["\'][^"\']+["\']\s*,'
)


class TSMissingToolAuthzRule(TSRule):
    """Detects MCP tool handlers without per-tool authorization checks."""

    @property
    def rule_id(self) -> str:
        return "TS-AUTHZ-001"

    @property
    def name(self) -> str:
        return "Missing Per-Tool Authorization"

    @property
    def severity(self) -> Severity:
        return Severity.MEDIUM

    @property
    def cwe(self) -> str:
        return "CWE-862"

    @property
    def description(self) -> str:
        return (
            "MCP tool handler executes without checking caller "
            "authorization or permissions."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "MCP tool handler executes without checking caller "
            "authorization or permissions.\n\n"
            "Even when transport-level authentication exists, each tool\n"
            "should verify the caller is authorized to perform that\n"
            "specific action. Without per-tool authorization, an\n"
            "authenticated user can invoke any tool regardless of role.\n\n"
            "Examples of vulnerable code:\n"
            "  server.tool('deleteUser', async (args) => {\n"
            "    await db.deleteUser(args.id);  // No authz check!\n"
            "  });\n\n"
            "How to fix:\n"
            "  - Check caller permissions before executing the tool\n"
            "  - Implement role-based access control (RBAC)\n"
            "  - Use authorization middleware per tool handler\n"
            "  - Verify token scopes match the tool's requirements"
        )

    def check(self, source: str) -> list[Finding]:
        """Check for missing per-tool authorization in tool handlers."""
        findings: list[Finding] = []
        lines = self._get_lines(source)

        # Find all server.tool() registrations
        tool_registrations = self._search_lines(source, TOOL_HANDLER_PATTERN)
        if not tool_registrations:
            return findings

        # Check if any authz patterns exist in the source
        has_global_authz = bool(PER_TOOL_AUTHZ_PATTERNS.search(source))
        if has_global_authz:
            return findings

        # No authz patterns found — flag each tool handler
        for lineno, line, _match in tool_registrations:
            # Extract tool name from the registration if possible
            name_match = re.search(
                r'server\s*\.\s*tool\s*\(\s*["\']([^"\']+)["\']', line
            )
            tool_name = name_match.group(1) if name_match else "unknown"

            findings.append(Finding(
                rule_id=self.rule_id,
                severity=self.severity,
                title="Missing Per-Tool Authorization",
                description=(
                    f"Tool '{tool_name}' handler has no authorization "
                    f"check. Any authenticated caller can invoke it."
                ),
                file="",
                line=lineno,
                code_snippet=line.strip()[:80],
                recommendation=(
                    "Add authorization checks (role/permission verification) "
                    "before executing the tool's logic."
                ),
                cwe=self.cwe,
            ))

        return findings
