"""
Manage DACLs on Windows

:depends:   - winreg Python module
"""

import logging
import os
import re

import salt.utils.platform
from salt.exceptions import CommandExecutionError

# TODO: Figure out the exceptions that could be raised and properly catch
#       them instead of a bare except that catches any exception at all
#       may also need to add the ability to take ownership of an object to set
#       permissions if the minion is running as a user and not LOCALSYSTEM


try:
    import winreg

    import ntsecuritycon
    import win32security

    HAS_WINDOWS_MODULES = True
except ImportError:
    HAS_WINDOWS_MODULES = False

log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = "win_dacl"


class daclConstants:
    """
    DACL constants used throughout the module
    """

    # Definition in ntsecuritycon is incorrect (does not match winnt.h). The version
    # in ntsecuritycon has the extra bits 0x200 enabled.
    # Note that you when you set this permission what you'll generally get back is it
    # ORed with 0x200 (SI_NO_ACL_PROTECT), which is what ntsecuritycon incorrectly defines.

    def __init__(self):
        self.FILE_ALL_ACCESS = (
            ntsecuritycon.STANDARD_RIGHTS_REQUIRED | ntsecuritycon.SYNCHRONIZE | 0x1FF
        )

        self.hkeys_security = {
            "HKEY_LOCAL_MACHINE": "MACHINE",
            "HKEY_USERS": "USERS",
            "HKEY_CURRENT_USER": "CURRENT_USER",
            "HKEY_CLASSES_ROOT": "CLASSES_ROOT",
            "MACHINE": "MACHINE",
            "USERS": "USERS",
            "CURRENT_USER": "CURRENT_USER",
            "CLASSES_ROOT": "CLASSES_ROOT",
            "HKLM": "MACHINE",
            "HKU": "USERS",
            "HKCU": "CURRENT_USER",
            "HKCR": "CLASSES_ROOT",
        }
        self.rights = {
            win32security.SE_REGISTRY_KEY: {
                "READ": {"BITS": winreg.KEY_READ, "TEXT": "read"},
                "FULLCONTROL": {"BITS": winreg.KEY_ALL_ACCESS, "TEXT": "full control"},
            },
            win32security.SE_FILE_OBJECT: {
                "READ": {"BITS": ntsecuritycon.FILE_GENERIC_READ, "TEXT": "read"},
                "WRITE": {"BITS": ntsecuritycon.FILE_GENERIC_WRITE, "TEXT": "write"},
                "READ&EXECUTE": {
                    "BITS": ntsecuritycon.FILE_GENERIC_EXECUTE
                    | ntsecuritycon.FILE_GENERIC_READ,
                    "TEXT": "read and execute",
                },
                "MODIFY": {
                    "BITS": ntsecuritycon.FILE_GENERIC_WRITE
                    | ntsecuritycon.FILE_GENERIC_READ
                    | ntsecuritycon.FILE_GENERIC_EXECUTE
                    | ntsecuritycon.DELETE,
                    "TEXT": "modify",
                },
                "FULLCONTROL": {"BITS": self.FILE_ALL_ACCESS, "TEXT": "full control"},
            },
        }
        self.validAceTypes = {
            "ALLOW": {"TEXT": "allowed", "BITS": 0},
            "DENY": {"TEXT": "denied", "BITS": 1},
        }
        self.validPropagations = {
            win32security.SE_REGISTRY_KEY: {
                "KEY": {"TEXT": "this key only", "BITS": win32security.NO_INHERITANCE},
                "KEY&SUBKEYS": {
                    "TEXT": "this key and subkeys",
                    "BITS": win32security.CONTAINER_INHERIT_ACE,
                },
                "SUBKEYS": {
                    "TEXT": "subkeys only",
                    "BITS": win32security.INHERIT_ONLY_ACE
                    | win32security.CONTAINER_INHERIT_ACE,
                },
                "THIS KEY ONLY": {
                    "TEXT": "this key only",
                    "BITS": win32security.NO_INHERITANCE,
                },
                "THIS KEY AND SUBKEYS": {
                    "TEXT": "this key and subkeys",
                    "BITS": win32security.CONTAINER_INHERIT_ACE,
                },
                "SUBKEYS ONLY": {
                    "TEXT": "subkeys only",
                    "BITS": win32security.INHERIT_ONLY_ACE
                    | win32security.CONTAINER_INHERIT_ACE,
                },
            },
            win32security.SE_FILE_OBJECT: {
                "FILE": {
                    "TEXT": "this file/folder only",
                    "BITS": win32security.NO_INHERITANCE,
                },
                "FOLDER": {
                    "TEXT": "this file/folder only",
                    "BITS": win32security.NO_INHERITANCE,
                },
                "FOLDER&SUBFOLDERS&FILES": {
                    "TEXT": "this folder, subfolders, and files",
                    "BITS": win32security.CONTAINER_INHERIT_ACE
                    | win32security.OBJECT_INHERIT_ACE,
                },
                "FOLDER&SUBFOLDERS": {
                    "TEXT": "this folder and subfolders",
                    "BITS": win32security.CONTAINER_INHERIT_ACE,
                },
                "FOLDER&FILES": {
                    "TEXT": "this folder and files",
                    "BITS": win32security.OBJECT_INHERIT_ACE,
                },
                "SUBFOLDERS&FILES": {
                    "TEXT": "subfolders and files",
                    "BITS": win32security.INHERIT_ONLY_ACE
                    | win32security.CONTAINER_INHERIT_ACE
                    | win32security.OBJECT_INHERIT_ACE,
                },
                "SUBFOLDERS": {
                    "TEXT": "subfolders only",
                    "BITS": win32security.INHERIT_ONLY_ACE
                    | win32security.CONTAINER_INHERIT_ACE,
                },
                "FILES": {
                    "TEXT": "files only",
                    "BITS": win32security.INHERIT_ONLY_ACE
                    | win32security.OBJECT_INHERIT_ACE,
                },
                "THIS FILE ONLY": {
                    "TEXT": "this file/folder only",
                    "BITS": win32security.NO_INHERITANCE,
                },
                "THIS FOLDER ONLY": {
                    "TEXT": "this file/folder only",
                    "BITS": win32security.NO_INHERITANCE,
                },
                "THIS FOLDER, SUBFOLDERS, AND FILES": {
                    "TEXT": "this folder, subfolders, and files",
                    "BITS": win32security.CONTAINER_INHERIT_ACE
                    | win32security.OBJECT_INHERIT_ACE,
                },
                "THIS FOLDER AND SUBFOLDERS": {
                    "TEXT": "this folder and subfolders",
                    "BITS": win32security.CONTAINER_INHERIT_ACE,
                },
                "THIS FOLDER AND FILES": {
                    "TEXT": "this folder and files",
                    "BITS": win32security.OBJECT_INHERIT_ACE,
                },
                "SUBFOLDERS AND FILES": {
                    "TEXT": "subfolders and files",
                    "BITS": win32security.INHERIT_ONLY_ACE
                    | win32security.CONTAINER_INHERIT_ACE
                    | win32security.OBJECT_INHERIT_ACE,
                },
                "SUBFOLDERS ONLY": {
                    "TEXT": "subfolders only",
                    "BITS": win32security.INHERIT_ONLY_ACE
                    | win32security.CONTAINER_INHERIT_ACE,
                },
                "FILES ONLY": {
                    "TEXT": "files only",
                    "BITS": win32security.INHERIT_ONLY_ACE
                    | win32security.OBJECT_INHERIT_ACE,
                },
            },
        }
        self.reflection_mask = {
            True: winreg.KEY_ALL_ACCESS,
            False: winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY,
        }
        self.objectType = {
            "FILE": win32security.SE_FILE_OBJECT,
            "DIRECTORY": win32security.SE_FILE_OBJECT,
            "REGISTRY": win32security.SE_REGISTRY_KEY,
        }

    def getObjectTypeBit(self, t):
        """
        returns the bit value of the string object type
        """
        if isinstance(t, str):
            t = t.upper()
            try:
                return self.objectType[t]
            except KeyError:
                raise CommandExecutionError(
                    'Invalid object type "{}". It should be one of the following:  {}'.format(
                        t, ", ".join(self.objectType)
                    )
                )
        else:
            return t

    def getSecurityHkey(self, s):
        """
        returns the necessary string value for an HKEY for the win32security module
        """
        try:
            return self.hkeys_security[s]
        except KeyError:
            raise CommandExecutionError(
                'No HKEY named "{}".  It should be one of the following:  {}'.format(
                    s, ", ".join(self.hkeys_security)
                )
            )

    def getPermissionBit(self, t, m):
        """
        returns a permission bit of the string permission value for the specified object type
        """
        try:
            if isinstance(m, str):
                return self.rights[t][m]["BITS"]
            else:
                return m
        except KeyError:
            raise CommandExecutionError(
                'No right "{}".  It should be one of the following:  {}'.format(
                    m, ", ".join(self.rights[t])
                )
            )

    def getPermissionText(self, t, m):
        """
        returns the permission textual representation of a specified permission bit/object type
        """
        try:
            return self.rights[t][m]["TEXT"]
        except KeyError:
            raise CommandExecutionError(
                'No right "{}".  It should be one of the following:  {}'.format(
                    m, ", ".join(self.rights[t])
                )
            )

    def getAceTypeBit(self, t):
        """
        returns the acetype bit of a text value
        """
        try:
            return self.validAceTypes[t]["BITS"]
        except KeyError:
            raise CommandExecutionError(
                'No ACE type "{}".  It should be one of the following:  {}'.format(
                    t, ", ".join(self.validAceTypes)
                )
            )

    def getAceTypeText(self, t):
        """
        returns the textual representation of a acetype bit
        """
        try:
            return self.validAceTypes[t]["TEXT"]
        except KeyError:
            raise CommandExecutionError(
                'No ACE type "{}".  It should be one of the following:  {}'.format(
                    t, ", ".join(self.validAceTypes)
                )
            )

    def getPropagationBit(self, t, p):
        """
        returns the propagation bit of a text value
        """
        try:
            return self.validPropagations[t][p]["BITS"]
        except KeyError:
            raise CommandExecutionError(
                'No propagation type of "{}".  It should be one of the following:  {}'.format(
                    p, ", ".join(self.validPropagations[t])
                )
            )

    def getPropagationText(self, t, p):
        """
        returns the textual representation of a propagation bit
        """
        try:
            return self.validPropagations[t][p]["TEXT"]
        except KeyError:
            raise CommandExecutionError(
                'No propagation type of "{}".  It should be one of the following:  {}'.format(
                    p, ", ".join(self.validPropagations[t])
                )
            )

    def processPath(self, path, objectType):
        """
        processes a path/object type combo and returns:
            registry types with the correct HKEY text representation
            files/directories with environment variables expanded
        """
        if objectType == win32security.SE_REGISTRY_KEY:
            splt = path.split("\\")
            hive = self.getSecurityHkey(splt.pop(0).upper())
            splt.insert(0, hive)
            path = r"\\".join(splt)
        else:
            path = os.path.expandvars(path)
        return path


def _get_user_sid(user):
    """
    return a state error dictionary, with 'sid' as a field if it could be returned
    if user is None, sid will also be None
    """
    ret = {}

    sid_pattern = r"^S-1(-\d+){1,}$"

    if user and re.match(sid_pattern, user, re.I):
        try:
            sid = win32security.GetBinarySid(user)
        except Exception as e:  # pylint: disable=broad-except
            ret["result"] = False
            ret["comment"] = (
                "Unable to obtain the binary security identifier for {}.  The exception"
                " was {}.".format(user, e)
            )
        else:
            try:
                win32security.LookupAccountSid("", sid)
                ret["result"] = True
                ret["sid"] = sid
            except Exception as e:  # pylint: disable=broad-except
                ret["result"] = False
                ret["comment"] = (
                    "Unable to lookup the account for the security identifier {}.  The"
                    " exception was {}.".format(user, e)
                )
    else:
        try:
            sid = win32security.LookupAccountName("", user)[0] if user else None
            ret["result"] = True
            ret["sid"] = sid
        except Exception as e:  # pylint: disable=broad-except
            ret["result"] = False
            ret["comment"] = (
                "Unable to obtain the security identifier for {}.  The exception"
                " was {}.".format(user, e)
            )
    return ret


def __virtual__():
    """
    Only works on Windows systems
    """
    if salt.utils.platform.is_windows() and HAS_WINDOWS_MODULES:
        return __virtualname__
    return (False, "Module win_dacl: module only works on Windows systems")


def _get_dacl(path, objectType):
    """
    Gets the DACL of a path
    """
    try:
        dacl = win32security.GetNamedSecurityInfo(
            path, objectType, win32security.DACL_SECURITY_INFORMATION
        ).GetSecurityDescriptorDacl()
    except Exception:  # pylint: disable=broad-except
        dacl = None
    return dacl


def get(path, objectType, user=None):
    """
    Get the ACL of an object. Will filter by user if one is provided.

    Args:

        path (str): The path to the object

        objectType (str): The type of object (FILE, DIRECTORY, REGISTRY)

        user (:obj:`str`, optional):
            A username to filter by.
            Default is ``None``.

    Returns (dict): A dictionary containing the ACL

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' win_dacl.get 'c:\\temp' directory
    """
    ret = {"Path": path, "ACLs": []}

    sidRet = _get_user_sid(user)

    if path and objectType:
        dc = daclConstants()
        objectTypeBit = dc.getObjectTypeBit(objectType)
        path = dc.processPath(path, objectTypeBit)
        tdacl = _get_dacl(path, objectTypeBit)
        if tdacl:
            for counter in range(0, tdacl.GetAceCount()):
                tAce = tdacl.GetAce(counter)
                if not sidRet["sid"] or (tAce[2] == sidRet["sid"]):
                    ret["ACLs"].append(_ace_to_text(tAce, objectTypeBit))
    return ret


def add_ace(path, objectType, user, permission, acetype, propagation):
    r"""
    Add an ace to an object

    Args:

        path (str):
            Path to the object (i.e. c:\\temp\\file, HKEY_LOCAL_MACHINE\\SOFTWARE\\KEY, etc)

        user (str): User to add

        permission (str): Permissions for the user

        acetype (str): Either allow/deny for each user/permission (ALLOW, DENY)

        propagation (str):
            How the ACE applies to children for Registry Keys and Directories
            (KEY, KEY&SUBKEYS, SUBKEYS)

    CLI Example:

    .. code-block:: bash

        # allow domain\fakeuser full control on HKLM\\SOFTWARE\\somekey, propagate to this key and subkeys
        salt 'myminion' win_dacl.add_ace 'HKEY_LOCAL_MACHINE\\SOFTWARE\\somekey' 'Registry' 'domain\fakeuser' 'FULLCONTROL' 'ALLOW' 'KEY&SUBKEYS'
    """
    ret = {"result": None, "changes": {}, "comment": ""}

    if path and user and permission and acetype and propagation:
        if objectType.upper() == "FILE":
            propagation = "FILE"
        dc = daclConstants()
        objectTypeBit = dc.getObjectTypeBit(objectType)
        path = dc.processPath(path, objectTypeBit)
        user = user.strip()
        permission = permission.strip().upper()
        acetype = acetype.strip().upper()
        propagation = propagation.strip().upper()

        sidRet = _get_user_sid(user)
        if not sidRet["result"]:
            return sidRet
        permissionbit = dc.getPermissionBit(objectTypeBit, permission)
        acetypebit = dc.getAceTypeBit(acetype)
        propagationbit = dc.getPropagationBit(objectTypeBit, propagation)
        dacl = _get_dacl(path, objectTypeBit)

        if dacl:
            acesAdded = []
            try:
                if acetypebit == 0:
                    dacl.AddAccessAllowedAceEx(
                        win32security.ACL_REVISION,
                        propagationbit,
                        permissionbit,
                        sidRet["sid"],
                    )
                elif acetypebit == 1:
                    dacl.AddAccessDeniedAceEx(
                        win32security.ACL_REVISION,
                        propagationbit,
                        permissionbit,
                        sidRet["sid"],
                    )
                win32security.SetNamedSecurityInfo(
                    path,
                    objectTypeBit,
                    win32security.DACL_SECURITY_INFORMATION,
                    None,
                    None,
                    dacl,
                    None,
                )
                acesAdded.append(
                    "{} {} {} on {}".format(
                        user,
                        dc.getAceTypeText(acetype),
                        dc.getPermissionText(objectTypeBit, permission),
                        dc.getPropagationText(objectTypeBit, propagation),
                    )
                )
                ret["result"] = True
            except Exception as e:  # pylint: disable=broad-except
                ret["comment"] = (
                    "An error occurred attempting to add the ace.  The error was {}".format(
                        e
                    )
                )
                ret["result"] = False
                return ret
            if acesAdded:
                ret["changes"]["Added ACEs"] = acesAdded
        else:
            ret["comment"] = f"Unable to obtain the DACL of {path}"
    else:
        ret["comment"] = "An empty value was specified for a required item."
        ret["result"] = False
    return ret


def rm_ace(path, objectType, user, permission=None, acetype=None, propagation=None):
    r"""
    remove an ace to an object

    Args:

        path (str):
            Path to the object (i.e. c:\\temp\\file, HKEY_LOCAL_MACHINE\\SOFTWARE\\KEY, etc)

        user (str): User to remove

        permission (:obj:`str`, optional):
            Permission for the user.
            Default is ``None``.

        acetype (:obj:`str`, optional):
            Either allow/deny for each user/permission (ALLOW, DENY).
            Default is ``None``.

        propagation (:obj:`str`, optional):
            How the ACE applies to children for Registry Keys and Directories
            (KEY, KEY&SUBKEYS, SUBKEYS).
            Default is ``None``.

    If any of the optional parameters are omitted (or set to None) they act as
    wildcards.

    CLI Example:

    .. code-block:: bash

        # Remove allow domain\fakeuser full control on HKLM\\SOFTWARE\\somekey propagated to this key and subkeys
        salt 'myminion' win_dacl.rm_ace 'Registry' 'HKEY_LOCAL_MACHINE\\SOFTWARE\\somekey' 'domain\fakeuser' 'FULLCONTROL' 'ALLOW' 'KEY&SUBKEYS'
    """
    ret = {"result": None, "changes": {}, "comment": ""}

    if path and user:
        dc = daclConstants()
        if propagation and objectType.upper() == "FILE":
            propagation = "FILE"
        objectTypeBit = dc.getObjectTypeBit(objectType)
        path = dc.processPath(path, objectTypeBit)

        user = user.strip()
        permission = permission.strip().upper() if permission else None
        acetype = acetype.strip().upper() if acetype else None
        propagation = propagation.strip().upper() if propagation else None

        if check_ace(path, objectType, user, permission, acetype, propagation, True)[
            "Exists"
        ]:
            sidRet = _get_user_sid(user)
            if not sidRet["result"]:
                return sidRet
            permissionbit = (
                dc.getPermissionBit(objectTypeBit, permission) if permission else None
            )
            acetypebit = dc.getAceTypeBit(acetype) if acetype else None
            propagationbit = (
                dc.getPropagationBit(objectTypeBit, propagation)
                if propagation
                else None
            )
            dacl = _get_dacl(path, objectTypeBit)
            counter = 0
            acesRemoved = []
            while counter < dacl.GetAceCount():
                tAce = dacl.GetAce(counter)
                if (
                    tAce[0][1] & win32security.INHERITED_ACE
                ) != win32security.INHERITED_ACE:
                    if tAce[2] == sidRet["sid"]:
                        if not acetypebit or tAce[0][0] == acetypebit:
                            if not propagationbit or (
                                (tAce[0][1] & propagationbit) == propagationbit
                            ):
                                if not permissionbit or tAce[1] == permissionbit:
                                    dacl.DeleteAce(counter)
                                    counter = counter - 1
                                    acesRemoved.append(
                                        _ace_to_text(tAce, objectTypeBit)
                                    )

                counter = counter + 1
            if acesRemoved:
                try:
                    win32security.SetNamedSecurityInfo(
                        path,
                        objectTypeBit,
                        win32security.DACL_SECURITY_INFORMATION,
                        None,
                        None,
                        dacl,
                        None,
                    )
                    ret["changes"]["Removed ACEs"] = acesRemoved
                    ret["result"] = True
                except Exception as e:  # pylint: disable=broad-except
                    ret["result"] = False
                    ret["comment"] = f"Error removing ACE.  The error was {e}."
                    return ret
        else:
            ret["comment"] = "The specified ACE was not found on the path."
    return ret


def _ace_to_text(ace, objectType):
    """
    helper function to convert an ace to a textual representation
    """
    dc = daclConstants()
    objectType = dc.getObjectTypeBit(objectType)
    try:
        userSid = win32security.LookupAccountSid("", ace[2])
        if userSid[1]:
            userSid = f"{userSid[1]}\\{userSid[0]}"
        else:
            userSid = f"{userSid[0]}"
    except Exception:  # pylint: disable=broad-except
        userSid = win32security.ConvertSidToStringSid(ace[2])
    tPerm = ace[1]
    tAceType = ace[0][0]
    tProps = ace[0][1]
    tInherited = ""
    for x in dc.validAceTypes:
        if dc.validAceTypes[x]["BITS"] == tAceType:
            tAceType = dc.validAceTypes[x]["TEXT"]
            break
    for x in dc.rights[objectType]:
        if dc.rights[objectType][x]["BITS"] == tPerm:
            tPerm = dc.rights[objectType][x]["TEXT"]
            break
    if (tProps & win32security.INHERITED_ACE) == win32security.INHERITED_ACE:
        tInherited = "[Inherited]"
        tProps = tProps ^ win32security.INHERITED_ACE
    for x in dc.validPropagations[objectType]:
        if dc.validPropagations[objectType][x]["BITS"] == tProps:
            tProps = dc.validPropagations[objectType][x]["TEXT"]
            break
    return f"{userSid} {tAceType} {tPerm} on {tProps} {tInherited}"


def _set_dacl_inheritance(path, objectType, inheritance=True, copy=True, clear=False):
    """
    helper function to set the inheritance

    Args:

        path (str): The path to the object

        objectType (str): The type of object

        inheritance (:obj:`bool`, optional):
            ``True`` enables inheritance, ``False`` disables.
            Default is ``True``.

        copy (:obj:`bool`, optional):
            Copy inherited ACEs to the DACL before disabling inheritance.
            Default is ``True``.

        clear (:obj:`bool`, optional):
            Remove non-inherited ACEs from the DACL.
            Default is ``False``.
    """
    ret = {"result": False, "comment": "", "changes": {}}

    if path:
        try:
            sd = win32security.GetNamedSecurityInfo(
                path, objectType, win32security.DACL_SECURITY_INFORMATION
            )
            tdacl = sd.GetSecurityDescriptorDacl()
            if inheritance:
                if clear:
                    counter = 0
                    removedAces = []
                    while counter < tdacl.GetAceCount():
                        tAce = tdacl.GetAce(counter)
                        if (
                            tAce[0][1] & win32security.INHERITED_ACE
                        ) != win32security.INHERITED_ACE:
                            tdacl.DeleteAce(counter)
                            removedAces.append(_ace_to_text(tAce, objectType))
                        else:
                            counter = counter + 1
                    if removedAces:
                        ret["changes"]["Removed ACEs"] = removedAces
                else:
                    ret["changes"]["Non-Inherited ACEs"] = "Left in the DACL"
                win32security.SetNamedSecurityInfo(
                    path,
                    objectType,
                    win32security.DACL_SECURITY_INFORMATION
                    | win32security.UNPROTECTED_DACL_SECURITY_INFORMATION,
                    None,
                    None,
                    tdacl,
                    None,
                )
                ret["changes"]["Inheritance"] = "Enabled"
            else:
                if not copy:
                    counter = 0
                    inheritedAcesRemoved = []
                    while counter < tdacl.GetAceCount():
                        tAce = tdacl.GetAce(counter)
                        if (
                            tAce[0][1] & win32security.INHERITED_ACE
                        ) == win32security.INHERITED_ACE:
                            tdacl.DeleteAce(counter)
                            inheritedAcesRemoved.append(_ace_to_text(tAce, objectType))
                        else:
                            counter = counter + 1
                    if inheritedAcesRemoved:
                        ret["changes"]["Removed ACEs"] = inheritedAcesRemoved
                else:
                    ret["changes"]["Previously Inherited ACEs"] = "Copied to the DACL"
                win32security.SetNamedSecurityInfo(
                    path,
                    objectType,
                    win32security.DACL_SECURITY_INFORMATION
                    | win32security.PROTECTED_DACL_SECURITY_INFORMATION,
                    None,
                    None,
                    tdacl,
                    None,
                )
                ret["changes"]["Inheritance"] = "Disabled"
            ret["result"] = True
        except Exception as e:  # pylint: disable=broad-except
            ret["result"] = False
            ret["comment"] = (
                f"Error attempting to set the inheritance.  The error was {e}."
            )

    return ret


def enable_inheritance(path, objectType, clear=False):
    """
    Enable/disable inheritance on an object

    Args:

        path (str): The path to the object

        objectType (str): The type of object (FILE, DIRECTORY, REGISTRY)

        clear (:obj:`bool`, optional):
            ``True`` will remove non-Inherited ACEs from the ACL.
            Default is ``False``.

    Returns (dict): A dictionary containing the results

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' win_dacl.enable_inheritance 'c:\\temp' directory
    """
    dc = daclConstants()
    objectType = dc.getObjectTypeBit(objectType)
    path = dc.processPath(path, objectType)

    return _set_dacl_inheritance(path, objectType, True, None, clear)


def disable_inheritance(path, objectType, copy=True):
    """
    Disable inheritance on an object

    Args:

        path (str): The path to the object

        objectType (str): The type of object (FILE, DIRECTORY, REGISTRY)

        copy (:obj:`bool`, optional):
            ``True`` will copy the Inherited ACEs to the DACL before
            disabling inheritance.
            Default is ``True``.

    Returns (dict): A dictionary containing the results

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' win_dacl.disable_inheritance 'c:\\temp' directory
    """
    dc = daclConstants()
    objectType = dc.getObjectTypeBit(objectType)
    path = dc.processPath(path, objectType)

    return _set_dacl_inheritance(path, objectType, False, copy, None)


def check_inheritance(path, objectType, user=None):
    """
    Check a specified path to verify if inheritance is enabled

    Args:

        path (str): path of the registry key or file system object to check

        objectType (str): The type of object (FILE, DIRECTORY, REGISTRY)

        user (:obj:`str`, optional):
            If provided, will consider only the ACEs for that user.
            Default is ``None``.

    Returns (bool): 'Inheritance' of True/False

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' win_dacl.check_inheritance 'c:\\temp' directory <username>
    """

    ret = {"result": False, "Inheritance": False, "comment": ""}

    sidRet = _get_user_sid(user)

    dc = daclConstants()
    objectType = dc.getObjectTypeBit(objectType)
    path = dc.processPath(path, objectType)

    try:
        sd = win32security.GetNamedSecurityInfo(
            path, objectType, win32security.DACL_SECURITY_INFORMATION
        )
        dacls = sd.GetSecurityDescriptorDacl()
    except Exception as e:  # pylint: disable=broad-except
        ret["result"] = False
        ret["comment"] = (
            f"Error obtaining the Security Descriptor or DACL of the path: {e}."
        )
        return ret

    for counter in range(0, dacls.GetAceCount()):
        ace = dacls.GetAce(counter)
        if (ace[0][1] & win32security.INHERITED_ACE) == win32security.INHERITED_ACE:
            if not sidRet["sid"] or ace[2] == sidRet["sid"]:
                ret["Inheritance"] = True
                break

    ret["result"] = True
    return ret


def check_ace(
    path,
    objectType,
    user,
    permission=None,
    acetype=None,
    propagation=None,
    exactPermissionMatch=False,
):
    """
    Checks a path to verify the ACE (access control entry) specified exists

    Args:

        path (str): Path to the file/reg key

        objectType (str): The type of object (FILE, DIRECTORY, REGISTRY)

        user (str): User that the ACL is for

        permission (:obj:`str`, optional):
            Permission to test for (READ, FULLCONTROL, etc).
            Default is ``None``.

        acetype (:obj:`str`, optional):
            The type of ACE (ALLOW or DENY).
            Default is ``None``.

        propagation (:obj:`str`, optional):
            The propagation type of the ACE (FILES, FOLDERS, KEY, KEY&SUBKEYS,
            SUBKEYS, etc).
            Default is ``None``.

        exactPermissionMatch (:obj:`bool`, optional):
            The ACL must match exactly, ie: if ``READ`` is specified, the user
            must have ``READ`` exactly and not ``FULLCONTROL`` (which also has
            the ``READ`` permission obviously)

    Returns (dict): 'Exists' true if the ACE exists, false if it does not

    CLI Example:

    .. code-block:: bash

        salt 'minion-id' win_dacl.check_ace 'c:\\temp' directory <username> fullcontrol
    """
    ret = {"result": False, "Exists": False, "comment": ""}

    dc = daclConstants()
    objectTypeBit = dc.getObjectTypeBit(objectType)
    path = dc.processPath(path, objectTypeBit)

    permission = permission.upper() if permission else None
    acetype = acetype.upper() if permission else None
    propagation = propagation.upper() if propagation else None

    permissionbit = (
        dc.getPermissionBit(objectTypeBit, permission) if permission else None
    )
    acetypebit = dc.getAceTypeBit(acetype) if acetype else None
    propagationbit = (
        dc.getPropagationBit(objectTypeBit, propagation) if propagation else None
    )

    sidRet = _get_user_sid(user)
    if not sidRet["result"]:
        return sidRet

    dacls = _get_dacl(path, objectTypeBit)
    ret["result"] = True
    if dacls:
        for counter in range(0, dacls.GetAceCount()):
            ace = dacls.GetAce(counter)
            if ace[2] == sidRet["sid"]:
                if not acetypebit or ace[0][0] == acetypebit:
                    if (
                        not propagationbit
                        or (ace[0][1] & propagationbit) == propagationbit
                    ):
                        if not permissionbit:
                            ret["Exists"] = True
                            return ret
                        if exactPermissionMatch:
                            if ace[1] == permissionbit:
                                ret["Exists"] = True
                                return ret
                        else:
                            if (ace[1] & permissionbit) == permissionbit:
                                ret["Exists"] = True
                                return ret
    else:
        ret["comment"] = "No DACL found for object."
    return ret
