1616import getpass
1717import os
1818import platform
19- import subprocess
2019from configparser import ConfigParser
2120from pathlib import Path
2221from typing import Any , Dict , List , Optional
2625from deadline .job_attachments .models import FileConflictResolution
2726
2827from ..exceptions import DeadlineOperationError
29- import re
3028
3129# Default path where AWS Deadline Cloud's configuration lives
3230CONFIG_FILE_PATH = os .path .join ("~" , ".deadline" , "config" )
@@ -182,80 +180,50 @@ def read_config() -> ConfigParser:
182180 return __config
183181
184182
185- def _get_grant_args (principal : str , permissions : str ) -> List [str ]:
186- return [
187- "/grant" ,
188- f"{ principal } :{ permissions } " ,
189- # Apply recursively
190- "/T" ,
191- ]
192-
193-
194- RE_ICACLS_OUTPUT = re .compile (r"^(.+?(?=\\))?(?:\\)?(.+?(?=:)):(.*)$" )
195-
196-
197- def _reset_directory_permissions_windows (directory : Path , username : str , permissions : str ) -> None :
183+ def _reset_directory_permissions_windows (directory : Path ) -> None :
198184 if platform .system () != "Windows" :
199185 return
186+ import win32security
187+ import ntsecuritycon
188+
189+ # We don't want to propagate existing permissions, so create a new DACL
190+ dacl = win32security .ACL ()
191+
192+ # On Windows, both SYSTEM and the Administrators group normally
193+ # have Full Access to files in the user's home directory.
194+ # Use SIDs to represent the Administrators and SYSTEM to
195+ # support multi-language operating systems
196+ # Administrator(S-1-5-32-544), SYSTEM(S-1-5-18)
197+ # https://learn.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
198+ system_sid = win32security .ConvertStringSidToSid ("S-1-5-18" )
199+ admin_sid = win32security .ConvertStringSidToSid ("S-1-5-32-544" )
200+
201+ username = getpass .getuser ()
202+ user_sid , _ , _ = win32security .LookupAccountName (None , username )
203+
204+ for sid in [user_sid , admin_sid , system_sid ]:
205+ dacl .AddAccessAllowedAceEx (
206+ win32security .ACL_REVISION ,
207+ ntsecuritycon .OBJECT_INHERIT_ACE | ntsecuritycon .CONTAINER_INHERIT_ACE ,
208+ ntsecuritycon .GENERIC_ALL ,
209+ sid ,
210+ )
200211
201- result = subprocess .run (
202- [
203- "icacls" ,
204- str (directory ),
205- ],
206- check = True ,
207- capture_output = True ,
208- text = True ,
212+ # Get the security descriptor of the object
213+ sd = win32security .GetFileSecurity (
214+ str (directory .resolve ()), win32security .DACL_SECURITY_INFORMATION
209215 )
210216
211- icacls_output = result .stdout
212-
213- principals_to_remove = []
214-
215- for line in icacls_output .splitlines ():
216- if line .startswith (str (directory )):
217- permission_line = line [len (str (directory )) :].strip ()
218- else :
219- permission_line = line .strip ()
220-
221- permissions_match = RE_ICACLS_OUTPUT .match (permission_line )
222- if permissions_match :
223- ad_group = permissions_match .group (1 )
224- ad_user = permissions_match .group (2 )
225- principal = f"{ ad_group } \\ { ad_user } "
226- if (
227- ad_user != username
228- and principal != "BUILTIN\\ Administrators"
229- and principal != "NT AUTHORITY\\ SYSTEM"
230- ):
231- principals_to_remove .append (ad_user )
232-
233- for principal in principals_to_remove :
234- subprocess .run (
235- [
236- "icacls" ,
237- str (directory ),
238- "/remove" ,
239- principal ,
240- ],
241- check = True ,
242- )
243-
244- subprocess .run (
245- [
246- "icacls" ,
247- str (directory ),
248- * _get_grant_args (username , permissions ),
249- # On Windows, both SYSTEM and the Administrators group normally
250- # have Full Access to files in the user's home directory.
251- # Use SIDs to represent the Administrators and SYSTEM to
252- # support multi-language operating systems
253- # Administrator(S-1-5-32-544), SYSTEM(S-1-5-18)
254- * _get_grant_args ("*S-1-5-32-544" , permissions ),
255- * _get_grant_args ("*S-1-5-18" , permissions ),
256- ],
257- check = True ,
258- capture_output = True ,
217+ # Set the security descriptor's DACL to the newly-created DACL
218+ # Arguments:
219+ # 1. bDaclPresent = 1: Indicates that the DACL is present in the security descriptor.
220+ # If set to 0, this method ignores the provided DACL and allows access to all principals.
221+ # 2. dacl: The discretionary access control list (DACL) to be set in the security descriptor.
222+ # 3. bDaclDefaulted = 0: Indicates the DACL was provided and not defaulted.
223+ # If set to 1, indicates the DACL was defaulted, as in the case of permissions inherited from a parent directory.
224+ sd .SetSecurityDescriptorDacl (1 , dacl , 0 )
225+ win32security .SetFileSecurity (
226+ str (directory .resolve ()), win32security .DACL_SECURITY_INFORMATION , sd
259227 )
260228
261229
@@ -268,15 +236,10 @@ def write_config(config: ConfigParser) -> None:
268236 a modified value from what `read_config` returns.
269237 """
270238 config_file_path = get_config_file_path ()
271- config_file_path .parent .mkdir (parents = True , exist_ok = True )
272-
273- if platform .system () == "Windows" :
274- username = getpass .getuser ()
275- config_file_parent_path = config_file_path .parent .absolute ()
276- # OI - Contained objects will inherit
277- # CI - Sub-directories will inherit
278- # F - Full control
279- _reset_directory_permissions_windows (config_file_parent_path , username , "(OI)(CI)(F)" )
239+ if not config_file_path .parent .exists ():
240+ config_file_path .parent .mkdir (parents = True , exist_ok = True )
241+ if platform .system () == "Windows" :
242+ _reset_directory_permissions_windows (config_file_path .parent )
280243
281244 # Using the config file path as the prefix ensures that the tmpfile and real file are
282245 # on the same filesystem. This is a requirement for os.replace to be atomic.
0 commit comments