Remove false dependency on pywin32
mattip opened this issue · 26 comments
trying now.. now pip is looking for alternate send2trash wheels when I ask "jupyter" directly... obscure new pip.
For now I can install till:
- jupyter-core,
- ipython,
- .. needing pywinpty, ... because of terminado, needed per jupyter-server and notebook..
so, apparently, pywinpty is my next blocker to Jupyter on PyPy3 windows, and I'm clueless to compile it
compiling Notebook is also intimidating... jupyter/notebook#6053
Note that the names of the modules that are used are not the same name as the package. pywin32 imports are used e.g. here:
jupyter_core/jupyter_core/paths.py
Lines 387 to 388 in e69a436
Thanks for the clarification. I guess this is a hard dependency then until
- pywin32 is broken up into more manageable pieces
- python + windows gets better built-in support for those security features
- someone re-writes those functions using ctypes instead.
- or some other alternative comes along
If it's the only blocker between Jupyter and PyPy on Windows, a rescue team will come ... re-checking if I can workaround
failed with this receipe:
- remove pywin32 dependancy and usage of it in jupyter_core,
- remove terminado dependancy on pywinpty (too hard to compile)
jupyter notebook launches the welcome page, but kernel crashes when creating a new notebook
winpython/winpython#982 (comment)
any good idea for next try welcomed (not an absolute failure on "jupyter notebook", but not good)
faling to compile pywin32 with PyPy3 too ... a bad luck day.
remove terminado dependancy on pywinpty (too hard to compile)
Compiles without issues but terminado might require an older version? https://www.lfd.uci.edu/~gohlke/pythonlibs/#pywinpty
someone re-writes those functions using ctypes instead.
should be easy, no?
I ran into another jupyter_core
relate issue:
Traceback (most recent call last):
File "X:\pypy3\site-packages\tornado\web.py", line 1704, in _execute
result = await result
File "X:\pypy3\site-packages\jupyter_server\services\contents\handlers.py", line 111, in get
path=path, type=type, format=format, content=content,
File "X:\pypy3\site-packages\jupyter_server\services\contents\filemanager.py", line 393, in get
model = self._dir_model(path, content=content)
File "X:\pypy3\site-packages\jupyter_server\services\contents\filemanager.py", line 293, in _dir_model
if self.allow_hidden or not is_file_hidden(os_path, stat_res=st):
File "X:\pypy3\site-packages\jupyter_core\paths.py", line 270, in is_file_hidden_win
if stat_res.st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN:
AttributeError: 'stat_result' object has no attribute 'st_file_attributes'
It seems PyPy never added that attribute, which was part of python3.5 for windows. I opened a PyPy issue.
I suppose "easy" for Christoph means "doable" for Steve Dower, but "hard" is a better word for me.
Baby step1
import ctypes
def get_display_name():
""" does win32api.GetUserNameEx(win32api.NameSamCompatible)"""
GetUserNameEx = ctypes.windll.secur32.GetUserNameExW
# from https://sjohannes.wordpress.com/2010/06/19/win32-python-getting-users-display-name-using-ctypes/
# NameDisplay = 3
NameDisplay = 2
size = ctypes.pointer(ctypes.c_ulong(0))
GetUserNameEx(NameDisplay, None, size)
nameBuffer = ctypes.create_unicode_buffer(size.contents.value)
GetUserNameEx(NameDisplay, nameBuffer, size)
return nameBuffer.value
for "LookupAccountName", I found someone who may have done it: https://github.com/MarioVilas/winappdbg
for "win32security.CreateWellKnownSid(win32security.WinBuiltinAdministratorsSid)", I found something there:
https://bugs.python.org/file41439/integrity_level.py
threre are pieces in https://github.com/openstack/os-win, but its depedencies end up using pywin32...
I suppose "easy" for Christoph means "doable" for Steve Dower, but "hard" is a better word for me.
Sorry, my comment wasn't meant to be serious. Using Win32 API and ctypes is very tedious and you'll end up with hundreds lines of code to replace a few lines of pywin32.
well, I was not sure it was "easy" or "not so complex" before trying, so I tried, as otherwise Jupyter will not work on PyPy3
otherwise Jupyter will not work on PyPy3
To experiment on PyPy3, you can use an empty win32_restrict_file_to_user
function.
well, it breaks when I open a ".ipynb" notebook. does it do the same for you ?
Probably unrelated. But yes, I get the same error. Others too.
someone re-writes those functions using ctypes instead.
Works for me, but not well tested. Maybe someone can try it:
def win32_restrict_file_to_user(fname):
"""Secure a windows file to read-only access for the user.
Follows guidance from win32 library creator:
http://timgolden.me.uk/python/win32_how_do_i/add-security-to-a-file.html
This method should be executed against an already generated file which
has no secrets written to it yet.
Parameters
----------
fname : unicode
The path to the file to secure
"""
import ctypes
from ctypes import wintypes
advapi32 = ctypes.WinDLL('advapi32', use_last_error=True)
secur32 = ctypes.WinDLL('secur32', use_last_error=True)
NameSamCompatible = 2
WinBuiltinAdministratorsSid = 26
DACL_SECURITY_INFORMATION = 4
ACL_REVISION = 2
ERROR_INSUFFICIENT_BUFFER = 122
ERROR_MORE_DATA = 234
SYNCHRONIZE = 0x100000
DELETE = 0x00010000
STANDARD_RIGHTS_REQUIRED = 0xF0000
STANDARD_RIGHTS_READ = 0x20000
STANDARD_RIGHTS_WRITE = 0x20000
FILE_READ_DATA = 1
FILE_READ_EA = 8
FILE_READ_ATTRIBUTES = 128
FILE_WRITE_DATA = 2
FILE_APPEND_DATA = 4
FILE_WRITE_EA = 16
FILE_WRITE_ATTRIBUTES = 256
FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF
FILE_GENERIC_READ = (
STANDARD_RIGHTS_READ
| FILE_READ_DATA
| FILE_READ_ATTRIBUTES
| FILE_READ_EA
| SYNCHRONIZE
)
FILE_GENERIC_WRITE = (
STANDARD_RIGHTS_WRITE
| FILE_WRITE_DATA
| FILE_WRITE_ATTRIBUTES
| FILE_WRITE_EA
| FILE_APPEND_DATA
| SYNCHRONIZE
)
class ACL(ctypes.Structure):
_fields_ = [
('AclRevision', wintypes.BYTE),
('Sbz1', wintypes.BYTE),
('AclSize', wintypes.WORD),
('AceCount', wintypes.WORD),
('Sbz2', wintypes.WORD),
]
PSID = ctypes.c_void_p
PACL = ctypes.POINTER(ACL)
PSECURITY_DESCRIPTOR = ctypes.POINTER(wintypes.BYTE)
def _nonzero_success(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
secur32.GetUserNameExW.errcheck = _nonzero_success
secur32.GetUserNameExW.restype = wintypes.BOOL
secur32.GetUserNameExW.argtypes = (
ctypes.c_int, # EXTENDED_NAME_FORMAT NameFormat
wintypes.LPWSTR, # LPWSTR lpNameBuffer,
wintypes.PULONG, # PULONG nSize
)
advapi32.CreateWellKnownSid.errcheck = _nonzero_success
advapi32.CreateWellKnownSid.restype = wintypes.BOOL
advapi32.CreateWellKnownSid.argtypes = (
wintypes.DWORD, # WELL_KNOWN_SID_TYPE WellKnownSidType
PSID, # PSID DomainSid
PSID, # PSID pSid
wintypes.PDWORD, # DWORD *cbSid
)
advapi32.LookupAccountNameW.errcheck = _nonzero_success
advapi32.LookupAccountNameW.restype = wintypes.BOOL
advapi32.LookupAccountNameW.argtypes = (
wintypes.LPWSTR, # LPCWSTR lpSystemName
wintypes.LPWSTR, # LPCWSTR lpAccountName
PSID, # PSID Sid
wintypes.LPDWORD, # LPDWORD cbSid
wintypes.LPWSTR, # LPCWSTR ReferencedDomainName
wintypes.LPDWORD, # LPDWORD cchReferencedDomainName
wintypes.LPDWORD, # PSID_NAME_USE peUse
)
advapi32.AddAccessAllowedAce.errcheck = _nonzero_success
advapi32.AddAccessAllowedAce.restype = wintypes.BOOL
advapi32.AddAccessAllowedAce.argtypes = (
PACL, # PACL pAcl
wintypes.DWORD, # DWORD dwAceRevision
wintypes.DWORD, # DWORD AccessMask
PSID, # PSID pSid
)
advapi32.SetSecurityDescriptorDacl.errcheck = _nonzero_success
advapi32.SetSecurityDescriptorDacl.restype = wintypes.BOOL
advapi32.SetSecurityDescriptorDacl.argtypes = (
PSECURITY_DESCRIPTOR, # PSECURITY_DESCRIPTOR pSecurityDescriptor
wintypes.BOOL, # BOOL bDaclPresent
PACL, # PACL pDacl
wintypes.BOOL, # BOOL bDaclDefaulted
)
advapi32.GetFileSecurityW.errcheck = _nonzero_success
advapi32.GetFileSecurityW.restype = wintypes.BOOL
advapi32.GetFileSecurityW.argtypes = (
wintypes.LPCWSTR, # LPCWSTR lpFileName
wintypes.DWORD, # SECURITY_INFORMATION RequestedInformation
PSECURITY_DESCRIPTOR, # PSECURITY_DESCRIPTOR pSecurityDescriptor
wintypes.DWORD, # DWORD nLength
wintypes.LPDWORD, # LPDWORD lpnLengthNeeded
)
advapi32.SetFileSecurityW.errcheck = _nonzero_success
advapi32.SetFileSecurityW.restype = wintypes.BOOL
advapi32.SetFileSecurityW.argtypes = (
wintypes.LPCWSTR, # LPCWSTR lpFileName
wintypes.DWORD, # SECURITY_INFORMATION SecurityInformation
PSECURITY_DESCRIPTOR, # PSECURITY_DESCRIPTOR pSecurityDescriptor
)
advapi32.MakeAbsoluteSD.errcheck = _nonzero_success
advapi32.MakeAbsoluteSD.restype = wintypes.BOOL
advapi32.MakeAbsoluteSD.argtypes = (
PSECURITY_DESCRIPTOR, # pSelfRelativeSecurityDescriptor
PSECURITY_DESCRIPTOR, # pAbsoluteSecurityDescriptor
wintypes.LPDWORD, # LPDWORD lpdwAbsoluteSecurityDescriptorSize
PACL, # PACL pDacl
wintypes.LPDWORD, # LPDWORD lpdwDaclSize
PACL, # PACL pSacl
wintypes.LPDWORD, # LPDWORD lpdwSaclSize
PSID, # PSID pOwner
wintypes.LPDWORD, # LPDWORD lpdwOwnerSize
PSID, # PSID pPrimaryGroup
wintypes.LPDWORD, # LPDWORD lpdwPrimaryGroupSize
)
advapi32.MakeSelfRelativeSD.errcheck = _nonzero_success
advapi32.MakeSelfRelativeSD.restype = wintypes.BOOL
advapi32.MakeSelfRelativeSD.argtypes = (
PSECURITY_DESCRIPTOR, # pAbsoluteSecurityDescriptor
PSECURITY_DESCRIPTOR, # pSelfRelativeSecurityDescriptor
wintypes.LPDWORD, # LPDWORD lpdwBufferLength
)
advapi32.InitializeAcl.errcheck = _nonzero_success
advapi32.InitializeAcl.restype = wintypes.BOOL
advapi32.InitializeAcl.argtypes = (
PACL, # PACL pAcl,
wintypes.DWORD, # DWORD nAclLength,
wintypes.DWORD, # DWORD dwAclRevision
)
def CreateWellKnownSid(WellKnownSidType):
# return a SID for predefined aliases
pSid = (ctypes.c_char * 1)()
cbSid = wintypes.DWORD()
try:
advapi32.CreateWellKnownSid(
WellKnownSidType, None, pSid, ctypes.byref(cbSid)
)
except OSError as e:
if e.winerror != ERROR_INSUFFICIENT_BUFFER:
raise
pSid = (ctypes.c_char * cbSid.value)()
advapi32.CreateWellKnownSid(
WellKnownSidType, None, pSid, ctypes.byref(cbSid)
)
return pSid[:]
def GetUserNameEx(NameFormat):
# return the user or other security principal associated with
# the calling thread
nSize = ctypes.pointer(ctypes.c_ulong(0))
try:
secur32.GetUserNameExW(NameFormat, None, nSize)
except WindowsError as e:
if e.winerror != ERROR_MORE_DATA:
raise
if not nSize.contents.value:
return None
lpNameBuffer = ctypes.create_unicode_buffer(nSize.contents.value)
secur32.GetUserNameExW(NameFormat, lpNameBuffer, nSize)
return lpNameBuffer.value
def LookupAccountName(lpSystemName, lpAccountName):
# return a security identifier (SID) for an account on a system
# and the name of the domain on which the account was found
cbSid = wintypes.DWORD(0)
cchReferencedDomainName = wintypes.DWORD(0)
peUse = wintypes.DWORD(0)
try:
advapi32.LookupAccountNameW(
lpSystemName,
lpAccountName,
None,
ctypes.byref(cbSid),
None,
ctypes.byref(cchReferencedDomainName),
ctypes.byref(peUse),
)
except WindowsError as e:
if e.winerror != ERROR_INSUFFICIENT_BUFFER:
raise
Sid = ctypes.create_unicode_buffer('', cbSid.value)
pSid = ctypes.cast(ctypes.pointer(Sid), wintypes.LPVOID)
lpReferencedDomainName = ctypes.create_unicode_buffer(
'', cchReferencedDomainName.value + 1
)
success = advapi32.LookupAccountNameW(
lpSystemName,
lpAccountName,
pSid,
ctypes.byref(cbSid),
lpReferencedDomainName,
ctypes.byref(cchReferencedDomainName),
ctypes.byref(peUse),
)
if not success:
raise ctypes.WinError()
return pSid, lpReferencedDomainName.value, peUse.value
def AddAccessAllowedAce(pAcl, dwAceRevision, AccessMask, pSid):
# add an access-allowed access control entry (ACE)
# to an access control list (ACL)
advapi32.AddAccessAllowedAce(pAcl, dwAceRevision, AccessMask, pSid)
def GetFileSecurity(lpFileName, RequestedInformation):
# return information about the security of a file or directory
nLength = wintypes.DWORD(0)
try:
advapi32.GetFileSecurityW(
lpFileName,
RequestedInformation,
None,
0,
ctypes.byref(nLength),
)
except WindowsError as e:
if e.winerror != ERROR_INSUFFICIENT_BUFFER:
raise
if not nLength.value:
return None
pSecurityDescriptor = (wintypes.BYTE * nLength.value)()
advapi32.GetFileSecurityW(
lpFileName,
RequestedInformation,
pSecurityDescriptor,
nLength,
ctypes.byref(nLength),
)
return pSecurityDescriptor
def SetFileSecurity(lpFileName, RequestedInformation, pSecurityDescriptor):
# set the security of a file or directory object
advapi32.SetFileSecurityW(
lpFileName, RequestedInformation, pSecurityDescriptor
)
def SetSecurityDescriptorDacl(
pSecurityDescriptor, bDaclPresent, pDacl, bDaclDefaulted
):
# set information in a discretionary access control list (DACL)
advapi32.SetSecurityDescriptorDacl(
pSecurityDescriptor, bDaclPresent, pDacl, bDaclDefaulted
)
def MakeAbsoluteSD(pSelfRelativeSecurityDescriptor):
# return a security descriptor in absolute format
# by using a security descriptor in self-relative format as a template
pAbsoluteSecurityDescriptor = None
lpdwAbsoluteSecurityDescriptorSize = wintypes.DWORD(0)
pDacl = None
lpdwDaclSize = wintypes.DWORD(0)
pSacl = None
lpdwSaclSize = wintypes.DWORD(0)
pOwner = None
lpdwOwnerSize = wintypes.DWORD(0)
pPrimaryGroup = None
lpdwPrimaryGroupSize = wintypes.DWORD(0)
try:
advapi32.MakeAbsoluteSD(
pSelfRelativeSecurityDescriptor,
pAbsoluteSecurityDescriptor,
ctypes.byref(lpdwAbsoluteSecurityDescriptorSize),
pDacl,
ctypes.byref(lpdwDaclSize),
pSacl,
ctypes.byref(lpdwSaclSize),
pOwner,
ctypes.byref(lpdwOwnerSize),
pPrimaryGroup,
ctypes.byref(lpdwPrimaryGroupSize),
)
except WindowsError as e:
if e.winerror != ERROR_INSUFFICIENT_BUFFER:
raise
pAbsoluteSecurityDescriptor = (
wintypes.BYTE * lpdwAbsoluteSecurityDescriptorSize.value
)()
pDaclData = (wintypes.BYTE * lpdwDaclSize.value)()
pDacl = ctypes.cast(pDaclData, PACL).contents
pSaclData = (wintypes.BYTE * lpdwSaclSize.value)()
pSacl = ctypes.cast(pSaclData, PACL).contents
pOwnerData = (wintypes.BYTE * lpdwOwnerSize.value)()
pOwner = ctypes.cast(pOwnerData, PSID)
pPrimaryGroupData = (wintypes.BYTE * lpdwPrimaryGroupSize.value)()
pPrimaryGroup = ctypes.cast(pPrimaryGroupData, PSID)
advapi32.MakeAbsoluteSD(
pSelfRelativeSecurityDescriptor,
pAbsoluteSecurityDescriptor,
ctypes.byref(lpdwAbsoluteSecurityDescriptorSize),
pDacl,
ctypes.byref(lpdwDaclSize),
pSacl,
ctypes.byref(lpdwSaclSize),
pOwner,
lpdwOwnerSize,
pPrimaryGroup,
ctypes.byref(lpdwPrimaryGroupSize),
)
return pAbsoluteSecurityDescriptor
def MakeSelfRelativeSD(pAbsoluteSecurityDescriptor):
# return a security descriptor in self-relative format
# by using a security descriptor in absolute format as a template
pSelfRelativeSecurityDescriptor = None
lpdwBufferLength = wintypes.DWORD(0)
try:
advapi32.MakeSelfRelativeSD(
pAbsoluteSecurityDescriptor,
pSelfRelativeSecurityDescriptor,
ctypes.byref(lpdwBufferLength),
)
except WindowsError as e:
if e.winerror != ERROR_INSUFFICIENT_BUFFER:
raise
pSelfRelativeSecurityDescriptor = (
wintypes.BYTE * lpdwBufferLength.value
)()
advapi32.MakeSelfRelativeSD(
pAbsoluteSecurityDescriptor,
pSelfRelativeSecurityDescriptor,
ctypes.byref(lpdwBufferLength),
)
return pSelfRelativeSecurityDescriptor
def NewAcl():
# return a new, initialized ACL (access control list) structure
nAclLength = 32767 # TODO: calculate this: ctypes.sizeof(ACL) + ?
acl_data = ctypes.create_string_buffer(nAclLength)
pAcl = ctypes.cast(acl_data, PACL).contents
advapi32.InitializeAcl(pAcl, nAclLength, ACL_REVISION)
return pAcl
SidAdmins = CreateWellKnownSid(WinBuiltinAdministratorsSid)
SidUser = LookupAccountName('', GetUserNameEx(NameSamCompatible))[0]
Acl = NewAcl()
AddAccessAllowedAce(Acl, ACL_REVISION, FILE_ALL_ACCESS, SidAdmins)
AddAccessAllowedAce(
Acl,
ACL_REVISION,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE,
SidUser,
)
SelfRelativeSD = GetFileSecurity(fname, DACL_SECURITY_INFORMATION)
AbsoluteSD = MakeAbsoluteSD(SelfRelativeSD)
SetSecurityDescriptorDacl(AbsoluteSD, 1, Acl, 0)
SelfRelativeSD = MakeSelfRelativeSD(AbsoluteSD)
SetFileSecurity(fname, DACL_SECURITY_INFORMATION, SelfRelativeSD)
Re AttributeError: FD attribute is write-only
: that looks like a bug in the cffi backend in pyzmq. The Cython backend works (build pyzmq with Cython and set the PYZMQ_BACKEND environment variable to 'cython').
Re AttributeError: FD attribute is write-only
. The pyzmq cffi backend treats SOCKET
s as int
, while on win_amd64 it should be size_t
or similar (UINT_PTR
). I'll open a pull request over at pyzmq once it's working.
Are you using the Cython backend? With the default cffi backend I get occasional crashes when closing sockets...
No clue, how do I know that ?
just did 1 test before my working day, to confirm early success
I don't see instability tonight in my longer experiments,
This has drifted quite off-topic for this issue. Please continue the discussion on the PyPy issue.