¿Cómo autorizar / denegar el acceso de escritura a un directorio en Windows usando Python?

Me gustaría poder autorizar o denegar el acceso de escritura a un directorio específico en Windows XP y más.

Intenté lo siguiente, y todos no funcionan:

  • os.chmod() : solo se puede especificar un atributo de solo lectura de archivo, consulte el documento de Python
  • win32api.SetFileAttribute() FILE_ATTRIBUTE_READONLY: un archivo que es de solo lectura. […] Este atributo no se respeta en los directorios , consulte SetFileAttribute de MSDN

Parece que la única alternativa que tengo es acceder y actualizar la ” Información de seguridad ” del directorio, he intentado durante varias horas hacer algo con esto sin mucho éxito (no estoy muy familiarizado con la API de Win32).

¿Alguna idea sobre cómo hacer eso?

Esto fue algo difícil de hacer. Comencé con esta gran respuesta que te ayuda con algo similar.

Puede comenzar simplemente listando las ACL para el directorio, lo que se podría hacer usando este código:

 import win32security import ntsecuritycon as con FILENAME = r'D:\tmp\acc_test' sd = win32security.GetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION) dacl = sd.GetSecurityDescriptorDacl() ace_count = dacl.GetAceCount() print('Ace count:', ace_count) for i in range(0, ace_count): rev, access, usersid = dacl.GetAce(i) user, group, type = win32security.LookupAccountSid('', usersid) print('User: {}/{}'.format(group, user), rev, access) 

Puede encontrar el método PyACL.GetAceCount() que devuelve el número de ACE.

La función GetAce(i) devuelve el encabezado ACCESS_ALLOWED_ACE como una tuple :

  • ACE_HEADER – dos enteros AceType , AceFlags – prueba y error me mostraron que AceFlags establecido en 11 significaba privilegios hereditarios y 3 no heredados
  • ACCESS_MASK – lista detallada aquí o en ntsecuritycon.py
  • SID

Ahora puede leer las ACE antiguas y eliminar las antiguas es bastante simple:

 for i in range(0, ace_count): dacl.DeleteAce(0) 

Y después de eso, simplemente puede agregar privilegios llamando a AddAccessAllowedAceEx() [ MSDN ]:

 userx, domain, type = win32security.LookupAccountName ("", "your.user") usery, domain, type = win32security.LookupAccountName ("", "other.user") dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 3, 2032127, userx) # Full control dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 3, 1179785, usery) # Read only sd.SetSecurityDescriptorDacl(1, dacl, 0) # may not be necessary win32security.SetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION, sd) 

Tomé los números 3 , 2032127 y 1179785 del listado en la primera mitad del script (antes de ejecutar el script, he configurado privilegios en Explorer-> Clic derecho-> Propiedades-> Seguridad-> Avanzada ):

Explorador-/> Clic derecho-> Propiedades-> Seguridad-> Avanzado”></p>
<p>  <sup>Solo imagen ilustrativa prestada de <a href=http://technet.microsoft.com/

 User: DOMAIN/user (0, 3) 2032127 User: DOMAIN/user2 (0, 3) 1179785 

Pero corresponde a:

  • 3 -> OBJECT_INHERIT_ACE|CONTAINER_INHERIT_ACE
  • 2032127 -> FILE_ALL_ACCESS (bueno, en realidad con.FILE_ALL_ACCESS = 2032639 , pero una vez que lo apliques al archivo y lo vuelvas a leer obtendrás 2032127 ; la diferencia es 512 – 0x0200 – la constante que no he encontrado en ntsecuritycon.py/file security permissions )
  • 1179785 -> FILE_GENERIC_READ

También puede eliminar el acceso, cambiarlo o eliminarlo, pero esto debería ser un comienzo muy sólido para usted.


TL; DR – códigos

 import win32security import ntsecuritycon as con FILENAME = r'D:\tmp\acc_test' userx, domain, type = win32security.LookupAccountName ("", "your.user") usery, domain, type = win32security.LookupAccountName ("", "other.user") sd = win32security.GetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION) dacl = sd.GetSecurityDescriptorDacl() ace_count = dacl.GetAceCount() print('Ace count:', ace_count) # Listing for i in range(0, ace_count): rev, access, usersid = dacl.GetAce(i) user, group, type = win32security.LookupAccountSid('', usersid) print('User: {}/{}'.format(group, user), rev, access) # Removing the old ones for i in range(0, ace_count): dacl.DeleteAce(0) # Add full control for user x dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, con.OBJECT_INHERIT_ACE|con.CONTAINER_INHERIT_ACE, con.FILE_ALL_ACCESS, userx) # Add read only access for user y dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, con.OBJECT_INHERIT_ACE|con.CONTAINER_INHERIT_ACE, con.FILE_GENERIC_READ, usery) sd.SetSecurityDescriptorDacl(1, dacl, 0) # may not be necessary win32security.SetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION, sd) 

Mini utilidad para el listado completo de ACE

Acabo de escribir un pequeño script para analizar todas las ACE de archivos:

 import win32security import ntsecuritycon as con import sys # List of all file masks that are interesting ACCESS_MASKS = ['FILE_READ_DATA', 'FILE_LIST_DIRECTORY', 'FILE_WRITE_DATA', 'FILE_ADD_FILE', 'FILE_APPEND_DATA', 'FILE_ADD_SUBDIRECTORY', 'FILE_CREATE_PIPE_INSTANCE', 'FILE_READ_EA', 'FILE_WRITE_EA', 'FILE_EXECUTE', 'FILE_TRAVERSE', 'FILE_DELETE_CHILD', 'FILE_READ_ATTRIBUTES', 'FILE_WRITE_ATTRIBUTES', 'FILE_ALL_ACCESS', 'FILE_GENERIC_READ', 'FILE_GENERIC_WRITE', 'FILE_GENERIC_EXECUTE'] # List of all inheritance flags ACE_FLAGS = ['OBJECT_INHERIT_ACE', 'CONTAINER_INHERIT_ACE', 'NO_PROPAGATE_INHERIT_ACE', 'INHERIT_ONLY_ACE'] # List of all ACE types ACE_TYPES = ['ACCESS_MIN_MS_ACE_TYPE', 'ACCESS_ALLOWED_ACE_TYPE', 'ACCESS_DENIED_ACE_TYPE', 'SYSTEM_AUDIT_ACE_TYPE', 'SYSTEM_ALARM_ACE_TYPE', 'ACCESS_MAX_MS_V2_ACE_TYPE', 'ACCESS_ALLOWED_COMPOUND_ACE_TYPE', 'ACCESS_MAX_MS_V3_ACE_TYPE', 'ACCESS_MIN_MS_OBJECT_ACE_TYPE', 'ACCESS_ALLOWED_OBJECT_ACE_TYPE', 'ACCESS_DENIED_OBJECT_ACE_TYPE', 'SYSTEM_AUDIT_OBJECT_ACE_TYPE', 'SYSTEM_ALARM_OBJECT_ACE_TYPE', 'ACCESS_MAX_MS_OBJECT_ACE_TYPE', 'ACCESS_MAX_MS_V4_ACE_TYPE', 'ACCESS_MAX_MS_ACE_TYPE', 'ACCESS_ALLOWED_CALLBACK_ACE_TYPE', 'ACCESS_DENIED_CALLBACK_ACE_TYPE', 'ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE', 'ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE', 'SYSTEM_AUDIT_CALLBACK_ACE_TYPE', 'SYSTEM_ALARM_CALLBACK_ACE_TYPE', 'SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE', 'SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE', 'SYSTEM_MANDATORY_LABEL_ACE_TYPE', 'ACCESS_MAX_MS_V5_ACE_TYPE'] ################################################################################ def get_ace_types_str(ace_type): ''' Yields all matching ACE types as strings ''' for t in ACE_TYPES: if getattr(con, t) == ace_type: yield t ################################################################################ def get_ace_flags_str(ace_flag): ''' Yields all matching ACE flags as strings ''' for t in ACE_FLAGS: attr = getattr(con, t) if (attr & ace_flag) == attr: yield t ################################################################################ def get_access_mask_str(access_mask): ''' Yields all matching ACE flags as strings ''' for t in ACCESS_MASKS: attr = getattr(con, t) if (attr & access_mask) == attr: yield t ################################################################################ def list_file_ace(filename): ''' Method for listing of file ACEs ''' # Load data sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION) dacl = sd.GetSecurityDescriptorDacl() # Print ACE count ace_count = dacl.GetAceCount() print('File', filename, 'has', ace_count, 'ACEs') # Go trough individual ACEs for i in range(0, ace_count): (ace_type, ace_flag), access_mask, usersid = dacl.GetAce(i) user, group, usertype = win32security.LookupAccountSid('', usersid) print('\tUser: {}\\{}'.format(group, user)) print('\t\tACE Type ({}):'.format(ace_type), '; '.join(get_ace_types_str(ace_type))) print('\t\tACE Flags ({}):'.format(ace_flag), ' | '.join(get_ace_flags_str(ace_flag))) print('\t\tAccess Mask ({}):'.format(access_mask), ' | '.join(get_access_mask_str(access_mask))) print() ################################################################################ # Execute with some defaults if __name__ == '__main__': for filename in sys.argv[1:]: list_file_ace(filename) print() 

Imprime cuerdas como esta:

 D:\tmp>acc_list.py D:\tmp D:\tmp\main.bat File D:\tmp has 8 ACEs User: BUILTIN\Administrators ACE Type (0): ACCESS_MIN_MS_ACE_TYPE; ACCESS_ALLOWED_ACE_TYPE ACE Flags (0): Access Mask (2032127): FILE_READ_DATA | FILE_LIST_DIRECTORY | FILE_WRITE_DATA | FILE_ADD_FILE | FILE_APPEND_DATA | FILE_ADD_SUBDIRECTORY | FILE_CREATE_PIPE_INSTANCE | FILE_READ_EA | FILE_WRITE_EA | FILE_EXECUTE | FILE_TRAVERSE | FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE ... 

Así que, buscando y tratando de entender lo que estaba pasando, logré encontrar algo muy similar a lo que @Vyktor publicó antes.

He encontrado ayuda usando este ejemplo .

Entonces, lo primero que hice fue tratar de entender los indicadores establecidos por Windows cuando estaba cambiando manualmente la información de seguridad con la GUI, construí un conjunto de funciones para ayudarme con esto:

 import os import win32con import win32security import win32process import ntsecuritycon d = "toto" f = os.path.join(d, "foo") def build_flags_map(*attrs, **kw): mod = kw.get('mod', win32con) r = {} for attr in attrs: value = getattr(mod, attr) r[value] = attr return r ACE_TYPE = build_flags_map('ACCESS_ALLOWED_ACE_TYPE', 'ACCESS_DENIED_ACE_TYPE') ACCESS_MASK = build_flags_map( 'GENERIC_WRITE', 'GENERIC_ALL', 'GENERIC_EXECUTE', 'GENERIC_READ', 'WRITE_OWNER', 'DELETE', 'READ_CONTROL', 'SYNCHRONIZE', 'WRITE_DAC', 'ACCESS_SYSTEM_SECURITY') ACCESS_MASK_FILES = build_flags_map( 'FILE_ADD_FILE', 'FILE_READ_DATA', 'FILE_LIST_DIRECTORY', 'FILE_WRITE_DATA', 'FILE_ADD_FILE', 'FILE_APPEND_DATA', 'FILE_ADD_SUBDIRECTORY', 'FILE_CREATE_PIPE_INSTANCE', 'FILE_READ_EA', 'FILE_WRITE_EA', 'FILE_EXECUTE', 'FILE_TRAVERSE', 'FILE_DELETE_CHILD', 'FILE_READ_ATTRIBUTES', 'FILE_WRITE_ATTRIBUTES', 'FILE_ALL_ACCESS', 'FILE_GENERIC_READ', 'FILE_GENERIC_WRITE', 'FILE_GENERIC_EXECUTE', mod=ntsecuritycon, ) ACE_FLAGS = build_flags_map( 'CONTAINER_INHERIT_ACE', 'INHERITED_ACE', 'FAILED_ACCESS_ACE_FLAG', 'INHERIT_ONLY_ACE', 'OBJECT_INHERIT_ACE', mod=win32security) def display_flags(map, value): r = [] for flag, name in map.items(): if flag & value: r.append(name) value = value - flag if value != 0: # We didn't specified all the flags in the mapping :( r.append('(flags left 0x%x)' % value) return r' | '.join(r) def show_acls(path): process_handler = win32process.GetCurrentProcess() thread_handler = win32security.OpenProcessToken( process_handler, win32security.TOKEN_ALL_ACCESS) current_sid = win32security.GetTokenInformation(thread_handler, win32security.TokenUser)[0] desc = win32security.GetNamedSecurityInfo( path, win32security.SE_FILE_OBJECT, win32security.DACL_SECURITY_INFORMATION) dacl = desc.GetSecurityDescriptorDacl() print("%d ACE on %s" % (dacl.GetAceCount(), path)) for i in range(0, dacl.GetAceCount()): ace = dacl.GetAce(i) (ace_type, ace_flags), ace_mask, ace_sid = ace if ace_sid == current_sid: user = "me" else: user = str(ace_sid) print(" User: %s =>\n" " ACE type: %s\n" " ACE flags: %s\n" " ACE mask: %s\n" " Raw ACE: %r\n" % ( user, ACE_TYPE[ace_type], display_flags(ACE_FLAGS, ace_flags), display_flags(ACCESS_MASK_FILES, ace_mask), ace)) 

A partir de ahí, obtuve la siguiente información:

 7 ACE on toto User: me => ACE type: ACCESS_DENIED_ACE_TYPE ACE flags: CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE ACE mask: FILE_ADD_FILE | FILE_CREATE_PIPE_INSTANCE | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA Raw ACE: ((1, 3), 278, ) User: me => ACE type: ACCESS_ALLOWED_ACE_TYPE ACE flags: INHERITED_ACE ACE mask: FILE_TRAVERSE | FILE_LIST_DIRECTORY | FILE_ADD_FILE | FILE_CREATE_PIPE_INSTANCE | FILE_READ_EA | FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_GENERIC_EXECUTE | FILE_WRITE_EA | FILE_ALL_ACCESS | (flags left 0x-12039f) Raw ACE: ((0, 16), 2032127, ) User: me => ACE type: ACCESS_ALLOWED_ACE_TYPE ACE flags: INHERITED_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE ACE mask: (flags left 0x10000000) Raw ACE: ((0, 27), 268435456, ) User: PySID:S-1-5-18 => ACE type: ACCESS_ALLOWED_ACE_TYPE ACE flags: INHERITED_ACE ACE mask: FILE_TRAVERSE | FILE_LIST_DIRECTORY | FILE_ADD_FILE | FILE_CREATE_PIPE_INSTANCE | FILE_READ_EA | FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_GENERIC_EXECUTE | FILE_WRITE_EA | FILE_ALL_ACCESS | (flags left 0x-12039f) Raw ACE: ((0, 16), 2032127, ) User: PySID:S-1-5-18 => ACE type: ACCESS_ALLOWED_ACE_TYPE ACE flags: INHERITED_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE ACE mask: (flags left 0x10000000) Raw ACE: ((0, 27), 268435456, ) User: PySID:S-1-5-32-544 => ACE type: ACCESS_ALLOWED_ACE_TYPE ACE flags: INHERITED_ACE ACE mask: FILE_TRAVERSE | FILE_LIST_DIRECTORY | FILE_ADD_FILE | FILE_CREATE_PIPE_INSTANCE | FILE_READ_EA | FILE_DELETE_CHILD | FILE_READ_ATTRIBUTES | FILE_GENERIC_EXECUTE | FILE_WRITE_EA | FILE_ALL_ACCESS | (flags left 0x-12039f) Raw ACE: ((0, 16), 2032127, ) User: PySID:S-1-5-32-544 => ACE type: ACCESS_ALLOWED_ACE_TYPE ACE flags: INHERITED_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE ACE mask: (flags left 0x10000000) Raw ACE: ((0, 27), 268435456, ) 

Este ejemplo muestra la ACL predeterminada que tengo en mi sistema + la primera, que es una que yo mismo creé y que niega escribe en el directorio.

Entonces, usando el ejemplo anterior, construí esto:

 def pipe_str_flags(map, *flags): r = 0 reverse_map = dict((value, key) for key, value in map.items()) for flag in flags: r = r | reverse_map[flag] return r def forbid_write(path): security_info = win32security.DACL_SECURITY_INFORMATION process_handler = win32process.GetCurrentProcess() thread_handler = win32security.OpenProcessToken( process_handler, win32security.TOKEN_ALL_ACCESS) desc = win32security.GetNamedSecurityInfo( path, win32security.SE_FILE_OBJECT, security_info) current_sid = win32security.GetTokenInformation(thread_handler, win32security.TokenUser)[0] dacl = desc.GetSecurityDescriptorDacl() mask = pipe_str_flags(ACCESS_MASK_FILES, 'FILE_ADD_FILE', 'FILE_CREATE_PIPE_INSTANCE', 'FILE_WRITE_ATTRIBUTES', 'FILE_WRITE_EA') ace_flags = pipe_str_flags(ACE_FLAGS, 'CONTAINER_INHERIT_ACE', 'OBJECT_INHERIT_ACE') dacl.AddAccessDeniedAceEx( dacl.GetAclRevision(), ace_flags, mask, current_sid) win32security.SetNamedSecurityInfo( path, win32security.SE_FILE_OBJECT, security_info, None, None, dacl, None) 

Al contrario de la solución de @Vyktor, estoy usando un ACE “Denegado”, al denegar el acceso de escritura (mientras que Vyktor agregó un ACE de “Sólo lectura permitida”).

Extraño una forma adecuada de eliminar este ACE para poder escribir de nuevo en este directorio, pero aún no he visto. Una cosa que es importante es que el ACE “Denegado” tiene prioridad sobre el ACE “Permitido”, por lo que probé la manera ingenua de usar dacl.AddAccessAllowedAceEx() con los mismos parámetros que estaba usando en dacl.AddAccessDeniedAceEx() , pero después, uno tiene prioridad sobre el anterior, por lo que todavía no puedo escribir en el directorio.