How to set folder permissions in Windows?
Asked Answered
C

8

33

I'm using Python to create a new personal folder when a users AD account is created. The folder is being created but the permissions are not correct. Can Python add the user to the newly created folder and change their permissions? I'm not sure where to begin coding this.

Crine answered 28/8, 2012 at 21:58 Comment(1)
You should really try to google with the title as searchstring, also welcome to Stackoverflow.Bhili
C
42

You want the win32security module, which is a part of pywin32. Here's an example of doing the sort of thing you want to do.

That example creates a new DACL for the file and replaces the old one, but it's easy to modify the existing one; all you need to do is get the existing DACL from the security descriptor instead of creating an empty one, like so:

import win32security
import ntsecuritycon as con

FILENAME = "whatever"

userx, domain, type = win32security.LookupAccountName ("", "User X")
usery, domain, type = win32security.LookupAccountName ("", "User Y")

sd = win32security.GetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION)
dacl = sd.GetSecurityDescriptorDacl()   # instead of dacl = win32security.ACL()

dacl.AddAccessAllowedAce(win32security.ACL_REVISION, con.FILE_GENERIC_READ | con.FILE_GENERIC_WRITE, userx)
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, con.FILE_ALL_ACCESS, usery)

sd.SetSecurityDescriptorDacl(1, dacl, 0)   # may not be necessary
win32security.SetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION, sd)
Cadwell answered 28/8, 2012 at 22:13 Comment(6)
In the example they are removing the DACL and replacing it. Is there a way to just edit the existing DACL? Just add read/write for USERX and full control for USERY?Crine
Your code worked great! One question, is there a way to find a list of all the security settings that can be set? With this example you used read/write and full. Are other possible setting available and documented? Again thanks for your help.Crine
Those are stored in ntsecuritycon (imported as con in this script) so just import ntsecuritycon and then dir(ntsecuritycon) or help(ntsecuritycon). Or [x for x in dir(ntsecuritycon) if x.startswith("FILE_")] should get you just the ones you need. These map to the NT permissions described in the Permissions table on this page.Cadwell
This answer is referenced frequently, but it uses low-level and obsolete functions. I've added an alternate answer that supports canonical ordering of ACEs and propagation of inheritable ACEs.Posting
hi @Cadwell you have any examples of denying permission on a folder using python on windows?Prole
@RiteshKarwa did you find a way to deny the permissions?Conspectus
P
12

Here's a version of kindall's answer that uses EXPLICIT_ACCESS entries with SetEntriesInAcl, which creates a proper ACL with the ACEs in canonical order (e.g. access-denied ACEs are listed first). Also, this version sets the DACL using SetNamedSecurityInfo, which supports propagating inheritable ACEs, unlike the obsolete function SetFileSecurity.

import ntsecuritycon
import win32security

FILENAME = "whatever"
USERX = "UserX"
USERY = "UserY"

entries = [{'AccessMode': win32security.GRANT_ACCESS,
            'AccessPermissions': 0,
            'Inheritance': win32security.CONTAINER_INHERIT_ACE |
                           win32security.OBJECT_INHERIT_ACE,
            'Trustee': {'TrusteeType': win32security.TRUSTEE_IS_USER,
                        'TrusteeForm': win32security.TRUSTEE_IS_NAME,
                        'Identifier': ''}}
            for i in range(2)]

entries[0]['AccessPermissions'] = (ntsecuritycon.GENERIC_READ |
                                   ntsecuritycon.GENERIC_WRITE)
entries[0]['Trustee']['Identifier'] = USERX
entries[1]['AccessPermissions'] = ntsecuritycon.GENERIC_ALL
entries[1]['Trustee']['Identifier'] = USERY

sd = win32security.GetNamedSecurityInfo(FILENAME, win32security.SE_FILE_OBJECT,
        win32security.DACL_SECURITY_INFORMATION)
dacl = sd.GetSecurityDescriptorDacl()
dacl.SetEntriesInAcl(entries)
win32security.SetNamedSecurityInfo(FILENAME, win32security.SE_FILE_OBJECT,
    win32security.DACL_SECURITY_INFORMATION |
    win32security.UNPROTECTED_DACL_SECURITY_INFORMATION,
    None, None, dacl, None)
Posting answered 6/4, 2017 at 2:34 Comment(0)
A
9

For those interested in the "list" of security descriptors for ACEs, what-have-ya use the following data structures. I had some help awhile back with this and have used this ever since.

typical_aces={
    2032127L:"Full Control(All)",
    1179817L:"Read(RX)",
    1180086L:"Add",
    1180095L:"Add&Read",
    1245631L:"Change"
}

binary_aces={
    1:"ACCESS_READ",            #0x00000001
    2:"ACCESS_WRITE",           #0x00000002
    4:"ACCESS_CREATE",          #0x00000004
    8:"ACCESS_EXEC",            #0x00000008
    16:"ACCESS_DELETE",         #0x00000010
    32:"ACCESS_ATRIB",          #0x00000020
    64:"ACCESS_PERM",           #0x00000040
    32768:"ACCESS_GROUP",       #0x00008000
    65536:"DELETE",             #0x00010000
    131072:"READ_CONTROL",      #0x00020000
    262144:"WRITE_DAC",         #0x00040000
    524288:"WRITE_OWNER",       #0x00080000
    1048576:"SYNCHRONIZE",      #0x00100000
    16777216:"ACCESS_SYSTEM_SECURITY",#0x01000000
    33554432:"MAXIMUM_ALLOWED", #0x02000000
    268435456:"GENERIC_ALL",    #0x10000000
    536870912:"GENERIC_EXECUTE",#0x20000000
    1073741824:"GENERIC_WRITE", #0x40000000
    65535:"SPECIFIC_RIGHTS_ALL",#0x0000ffff
    983040:"STANDARD_RIGHTS_REQUIRED",#0x000f0000
    2031616:"STANDARD_RIGHTS_ALL",#0x001f0000
    }

Pass the mask from a given DACL / path to:

def calculate_plaintext_mask(mask):
    a=2147483648L
    if typical_aces.has_key(mask):
        return typical_aces[mask]
    else:
        result='NONE'
        while a>>1:
            a=a>>1
            masked=mask&a
            if masked:
                if binary_aces.has_key(masked):
                    result=binary_aces[masked]+':'+result
    return result
Ante answered 23/1, 2014 at 23:49 Comment(1)
I'll amend this to suggest that anyone working with directory ownership / ACLs become familiar with all the privileges that can be assigned to an account. For example -- taking ownership fails frequently because people don't assign "SeRestorePrivilege" to their account. You will almost certainly need to use PInvoke ("System.Runtime.InteropServices") called from within your code. I for one have switched to calling WinAPI like advapi32.dll to ensure I am hooking in correctly.Ante
G
8

Here's a full pythonic way of setting file ownership / ACLs recursively or not on NTFS files under windows, with or without ACL inheritance.

[EDIT]

I now have released this code as Python package windows_tools.file_utils, see the source code here

[/EDIT]

First we need a function that can take ownership of files (set_file_owner()) Then we need a function that can handle ACLs on Windows (set_acls()).

In order to make permissions easy, we'll have a function (easy_permissions()) that translates R, RX, M, F permissions into permission bitmasks.

Once we got that, we may just os.listdir recursively into a directory (using get_files_recursive()), and execute a function on PermissionError to deal with the permissions. Once this is done, just loop over all files again and set permissions accordingly.

The underlying code:

import os
from fnmatch import fnmatch
from itertools import chain
import win32api
import win32security
import ntsecuritycon
import pywintypes


def glob_path_match(path, pattern_list):
    """
    Checks if path is in a list of glob style wildcard paths
    :param path: path of file / directory
    :param pattern_list: list of wildcard patterns to check for
    :return: Boolean
    """
    return any(fnmatch(path, pattern) for pattern in pattern_list)


def get_files_recursive(root, d_exclude_list=None, f_exclude_list=None,
                    ext_exclude_list=None, ext_include_list=None,
                    depth=0, primary_root=None, fn_on_perm_error=None,
                    include_dirs=False):
"""
Walk a path to recursively find files
Modified version of https://mcmap.net/q/270404/-how-to-traverse-through-the-files-in-a-directory that includes exclusion lists
and accepts glob style wildcards on files and directories
:param root: (str) path to explore
:param include_dirs: (bool) should output list include directories
:param d_exclude_list: (list) list of root relative directories paths to exclude
:param f_exclude_list: (list) list of filenames without paths to exclude
:param ext_exclude_list: list() list of file extensions to exclude, ex: ['.log', '.bak'],
       takes precedence over ext_include_list
:param ext_include_lsit: (list) only include list of file extensions, ex: ['.py']
:param depth: (int) depth of recursion to acheieve, 0 means unlimited, 1 is just the current dir...
:param primary_root: (str) Only used for internal recursive exclusion lookup, don't pass an argument here
:param fn_on_perm_error: (function) Optional function to pass, which argument will be the file / directory that has permission errors
:return: list of files found in path
"""

# Make sure we don't get paths with antislashes on Windows
if os.path.isdir(root):
    root = os.path.normpath(root)
else:
    return root

# Check if we are allowed to read directory, if not, try to fix permissions if fn_on_perm_error is passed
try:
    os.listdir(root)
except PermissionError:
    if fn_on_perm_error is not None:
        fn_on_perm_error(root)

# Make sure we clean d_exclude_list only on first function call
if primary_root is None:
    if d_exclude_list is not None:
        # Make sure we use a valid os separator for exclusion lists
        d_exclude_list = [os.path.normpath(d) for d in d_exclude_list]
    else:
        d_exclude_list = []
if f_exclude_list is None:
    f_exclude_list = []
if ext_exclude_list is None:
    ext_exclude_list = []

def _find_files():
    try:
        if include_dirs:
            yield root
        for f in os.listdir(root):
            file_ext = os.path.splitext(f)[1]
            if os.path.isfile(os.path.join(root, f)) and not glob_path_match(f, f_exclude_list) \
                and file_ext not in ext_exclude_list \
                and (file_ext in ext_include_list if ext_include_list is not None else True):
                yield os.path.join(root, f)

    except PermissionError:
        pass

def _find_files_in_dirs(depth):
    if depth == 0 or depth > 1:
        depth = depth - 1 if depth > 1 else 0
        try:
            for d in os.listdir(root):
                d_full_path = os.path.join(root, d)
                if os.path.isdir(d_full_path):
                    # p_root is the relative root the function has been called with recursively
                    # Let's check if p_root + d is in d_exclude_list
                    p_root = os.path.join(primary_root, d) if primary_root is not None else d
                    if not glob_path_match(p_root, d_exclude_list):
                        files_in_d = get_files_recursive(d_full_path,
                                                         d_exclude_list=d_exclude_list,
                                                         f_exclude_list=f_exclude_list,
                                                         ext_exclude_list=ext_exclude_list,
                                                         ext_include_list=ext_include_list,
                                                         depth=depth, primary_root=p_root,
                                                         fn_on_perm_error=fn_on_perm_error,
                                                         include_dirs=include_dirs)
                        if include_dirs:
                            yield d
                        if files_in_d:
                            for f in files_in_d:
                                yield f

        except PermissionError:
            pass

# Chain both generators
return chain(_find_files(), _find_files_in_dirs(depth))


def get_binary_sid(string=None):
    """
    Wrapper function that returns PySID object from SID identifier or username
    If none given, we'll get current user

    :param string: (str) SID identifier or username
    :return: (PySID) object
    """
    if string is None:
        string = win32api.GetUserName()
    if string.startswith('S-1-'):
        # Consider we deal with a sid string
        return win32security.GetBinarySid(string)
    else:
        # Try to resolve username
        # LookupAccountName returns tuple (user, domain, type)

        try:
            user, _, _ = win32security.LookupAccountName('', string)
            print(user)
            return user
        except pywintypes.error as e:
            raise OSError('Cannot map security ID: {0} with name. {1}'.format(string, e))


def set_file_owner(path, owner=None, force=False):
    """
    Set owner on NTFS files / directories
    https://mcmap.net/q/452727/-taking-directory-ownership-on-windows-with-python-results-in-quot-access-denied-quot-error

    :param path: (str) path
    :param owner: (PySID) object that represents the security identifier. If not set, current security identifier will be used
    :param force: (bool) Shall we force take ownership
    :return:
    """
    try:
        hToken = win32security.OpenThreadToken(win32api.GetCurrentThread(),
                                               win32security.TOKEN_ALL_ACCESS, True)

    except win32security.error:
        hToken = win32security.OpenProcessToken(win32api.GetCurrentProcess(),
                                                win32security.TOKEN_ALL_ACCESS)
    if owner is None:
        owner = win32security.GetTokenInformation(hToken, win32security.TokenOwner)
    prev_state = ()
    if force:
        new_state = [(win32security.LookupPrivilegeValue(None, name),
                      win32security.SE_PRIVILEGE_ENABLED)
                     for name in (win32security.SE_TAKE_OWNERSHIP_NAME,
                                  win32security.SE_RESTORE_NAME)]
        prev_state = win32security.AdjustTokenPrivileges(hToken, False,
                                                         new_state)
    try:
        sd = win32security.SECURITY_DESCRIPTOR()
        sd.SetSecurityDescriptorOwner(owner, False)
        win32security.SetFileSecurity(path, win32security.OWNER_SECURITY_INFORMATION, sd)
    except pywintypes.error as e:
        # Let's raise OSError so we don't need to import pywintypes in parent module to catch the exception
        raise OSError('Cannot take ownership of file: {0}. {1}.'.format(path, e))
    finally:
        if prev_state:
            win32security.AdjustTokenPrivileges(hToken, False, prev_state)


def easy_permissions(permission):
    """
    Creates ntsecuritycon permission bitmask from simple rights

    :param permission: (str) Simple R, RX, RWX, F  rights
    :return: (int) ntsecuritycon permission bitmask
    """
    permission = permission.upper()
    if permission == 'R':
        return ntsecuritycon.GENERIC_READ
    if permission == 'RX':
        return ntsecuritycon.GENERIC_READ | ntsecuritycon.GENERIC_EXECUTE
    if permission in ['RWX', 'M']:
        return ntsecuritycon.GENERIC_READ | ntsecuritycon.GENERIC_WRITE | ntsecuritycon.GENERIC_EXECUTE
    if permission == 'F':
        return ntsecuritycon.GENERIC_ALL
    raise ValueError('Bogus easy permission')


def set_acls(path, user_list=None, group_list=None, owner=None, permission=None, inherit=False, inheritance=False):
    """
    Set Windows DACL list

    :param path: (str) path to directory/file
    :param user_sid_list: (list) str usernames or PySID objects
    :param group_sid_list: (list) str groupnames or PySID objects
    :param owner: (str) owner name or PySID obect
    :param permission: (int) permission bitmask
    :param inherit: (bool) inherit parent permissions
    :param inheritance: (bool) apply ACL to sub folders and files
    """
    if inheritance:
        inheritance_flags = win32security.CONTAINER_INHERIT_ACE | win32security.OBJECT_INHERIT_ACE
    else:
        inheritance_flags = win32security.NO_INHERITANCE

    security_descriptor = {'AccessMode': win32security.GRANT_ACCESS,
                           'AccessPermissions': 0,
                           'Inheritance': inheritance_flags,
                           'Trustee': {'TrusteeType': '',
                                       'TrusteeForm': win32security.TRUSTEE_IS_SID,
                                       'Identifier': ''}
                           }

    # Now create a security descriptor for each user in the ACL list
    security_descriptors = []

    # If no user / group is defined, let's take current user
    if user_list is None and group_list is None:
        user_list = [get_binary_sid()]

    if user_list is not None:
        for sid in user_list:
            sid = get_binary_sid(sid)
            s = security_descriptor
            s['AccessPermissions'] = permission
            s['Trustee']['TrusteeType'] = win32security.TRUSTEE_IS_USER
            s['Trustee']['Identifier'] = sid
            security_descriptors.append(s)

    if group_list is not None:
        for sid in group_list:
            sid = get_binary_sid(sid)
            s = security_descriptor
            s['AccessPermissions'] = permission
            s['Trustee']['TrusteeType'] = win32security.TRUSTEE_IS_GROUP
            s['Trustee']['Identifier'] = sid
            security_descriptors.append(s)

    try:
        sd = win32security.GetNamedSecurityInfo(path, win32security.SE_FILE_OBJECT,
                                                win32security.DACL_SECURITY_INFORMATION | win32security.UNPROTECTED_DACL_SECURITY_INFORMATION)
    except pywintypes.error as e:
        raise OSError('Failed to read security for file: {0}. {1}'.format(path, e))
    dacl = sd.GetSecurityDescriptorDacl()
    dacl.SetEntriesInAcl(security_descriptors)

    security_information_flags = win32security.DACL_SECURITY_INFORMATION

    if not inherit:
        # PROTECTED_DACL_SECURITY_INFORMATION disables inheritance from parent
        security_information_flags = security_information_flags | win32security.PROTECTED_DACL_SECURITY_INFORMATION
    else:
        security_information_flags = security_information_flags | win32security.UNPROTECTED_DACL_SECURITY_INFORMATION

    # If we want to change owner, SetNamedSecurityInfo will need win32security.OWNER_SECURITY_INFORMATION in SECURITY_INFORMATION
    if owner is not None:
        security_information_flags = security_information_flags | win32security.OWNER_SECURITY_INFORMATION
        if isinstance(owner, str):
            owner = get_binary_sid(owner)

    try:
        # SetNamedSecurityInfo(path, object_type, security_information, owner, group, dacl, sacl)
        win32security.SetNamedSecurityInfo(path, win32security.SE_FILE_OBJECT,
                                           security_information_flags,
                                           owner, None, dacl, None)
    except pywintypes.error as e:
        raise OSError


def take_ownership_recursive(path, owner=None):
    def take_own(path):
        nonlocal owner
        try:
            set_file_owner(path, owner=owner, force=True)
        except OSError:
            print('Permission error on: {0}.'.format(path))

    files = get_files_recursive(path, include_dirs=True, fn_on_perm_error=take_own)
    for file in files:
        set_file_owner(file, force=True)


def get_files_recursive_and_set_permissions(path, owner=None, permissions=None, user_list=None):
    def fix_perms(path):
        nonlocal permissions
        nonlocal owner
        nonlocal user_list
        if permissions == None:
            permissions = easy_permissions('F')
        print('Permission error on: {0}.'.format(path))
        try:
            set_acls(path, user_list=user_list, owner=owner, permission=permissions, inheritance=False)
        except OSError:
            # Lets force ownership change
            try:
                set_file_owner(path, force=True)
                # Now try again
                set_acls(path, user_list=user_list, owner=owner, permission=permissions, inheritance=False)
            except OSError as e:
                print('Cannot fix permission on {0}. {1}'.format(path, e))

    files = get_files_recursive(path, include_dirs=True, fn_on_perm_error=fix_perms)
    for file in files:
        set_acls(file, user_list=user_list, owner=owner, permission=easy_permissions('F'), inheritance=False)

Here are some examples of how to use the code:

# Recursively set owner
take_ownership_recursive(r'C:\MYPATH', owner=get_binary_sid('MyUser'))

# Recursively set permissions
get_files_recursive_and_set_permissions(r'C;\MYPATH', permissions=easy_permissions('F'), user_list=['MyUser', 'MyOtherUser'])

# Recursively set permissions with inheritance
get_files_recursive_and_set_permissions(r'C:\MYPATH', permissions=easy_permissions('RX'), user_list=['S-1-5-18'], inheritance=True)

# Set permissions
set_acls(r'C:\MYPATH', permissions=easy_permissions('F'), user_list['MyUser'])

# Take ownership
set_file_owner(r'C:\MYPATH', owner=get_binary_sid('MyUser'), Force=True)

Big thanks to Eryk Sun for all his posts about win32security file handling in Python, those made it possible to write the proper code. See https://mcmap.net/q/441263/-how-to-set-folder-permissions-in-windows and https://mcmap.net/q/452727/-taking-directory-ownership-on-windows-with-python-results-in-quot-access-denied-quot-error

Goodale answered 5/4, 2020 at 10:55 Comment(0)
K
2

Here's the simplest solution (where you also can specify the user and add a modify premission):

command = r'icacls "C:\Users\PC\PycharmProjects\IIS_configuration\someFolder" /grant "userA":(OI)(CI)RWRXM'
appcmd = subprocess.run(command, shell=True, check=True)

To see how it works you can run via CMD:

icacls "C:\Users\PC\PycharmProjects\IIS_configuration\someFolder" /grant "userA":(OI)(CI)RWRXM

I used icacls (windows commands) - you can find information about it here and here.

And then I just ran this command with python subprocess

Info about this specific command:

  • "C:\Users\PC\PycharmProjects\IIS_configuration\someFolder" - dst
  • userA - The name of the user
  • (OI) - Object inherit
  • (CI) -Container inherit
  • RWRXM - each word marks a premission. Like R is read while RX is Read and execute access (info here).
Killigrew answered 6/1, 2021 at 15:32 Comment(0)
G
2

Windows folder permissions can be assigned using the Python for .NET package. This package lets programmers use .NET constructs directly within Python. One advantage of this approach is the robust online documentation for .NET that will assist in identifying the specific function calls and permissions to use. For example, all possible file/folder permissions are documented in the article for the FileSystemRights Enum.

Note that trying to access an Enum value of "None" directly as an attribute results in a Python syntax error. The getattr() function can be used as a workaround.

import clr
import System

from System.IO import Directory
from System.Security.AccessControl import (
    AccessControlType,
    FileSystemAccessRule,
    FileSystemRights,
    InheritanceFlags,
    PropagationFlags,
)

path = r"C:\path\to\folder"
accessControl = Directory.GetAccessControl(path)
accessRule = FileSystemAccessRule(
    "UserX",
    FileSystemRights.Modify | FileSystemRights.Synchronize,
    InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
    getattr(PropagationFlags, "None"),
    AccessControlType.Allow,
)
accessControl.AddAccessRule(accessRule)
Directory.SetAccessControl(path, accessControl)
Grandfatherly answered 7/1, 2021 at 18:56 Comment(2)
If you're curious why I'm granting the "Synchronize" permission, check out this other StackOverflow answer: https://mcmap.net/q/262037/-add-quot-everyone-quot-privilege-to-folder-using-c-netGrandfatherly
Great solution, having the .NET-universe at your fingertips. Worth mentioning, that this won't work within WSL, though ("Platform not supported"), as Python.NET doesn't fully support Mono.Furunculosis
M
-1

For starters, the user's profile directory is created automatically if it does not exist, and the permissions are set to reasonable defaults. Unless you have a specific need to use python, you could just let windows create the folder and sort permissions out for you.

If you wish to use python anyway, you could consider just using os.system() to call cacls or icacls with the correct arguments. And instead of permissions, you might simply need to change the folder's owner to the user who will own the folder.

Good luck with your endeavours.

Meitner answered 29/8, 2012 at 18:12 Comment(0)
D
-2

use os.chmod

http://docs.python.org/library/os.html#os.chmod

you can set the permissions with os.chmod

The mod is written in base 8, if you convert it to binary it would be

000 111 111 000 rwx rwx rwx The first rwx is for owner, the second is for the group and the third is for world

r=read,w=write,x=execute

The permissions you see most often are 7 read/write/execute - you need execute for directories to see the contents 6 read/write 4 readonly

When you use os.chmod it makes most sense to use octal notation so

os.chmod('myfile',0o666)  # read/write by everyone
os.chmod('myfile',0o644)  # read/write by me, readable for everone else

Remember I said you usually want directories to be "executable" so you can see the contents.

os.chmod('mydir',0o777)  # read/write by everyone
os.chmod('mydir',0o755)  # read/write by me, readable for everone else

Note: The syntax of 0o777 is for Python 2.6 and 3+. otherwise for the 2 series it is 0777. 2.6 accepts either syntax so the one you choose will depend on whether you want to be forward or backward compatible.

Desmonddesmoulins answered 28/8, 2012 at 22:5 Comment(1)
In Windows, the applicability of chmod is rather reduced. From the chmod doc: "Although Windows supports chmod(), you can only set the file’s read-only flag with it (via the stat.S_IWRITE and stat.S_IREAD constants or a corresponding integer value). All other bits are ignored."Stroganoff

© 2022 - 2024 — McMap. All rights reserved.