-
-
Notifications
You must be signed in to change notification settings - Fork 365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check and adjust permissions before kernel spec is written #535
base: main
Are you sure you want to change the base?
Changes from all commits
57485af
bae4d33
1752d72
424f598
f22f5b4
6bfcab8
dfec211
b5b4770
09d4345
74c6bbb
61bdc3f
b2e1a54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,35 +61,45 @@ def get_kernel_dict(extra_arguments=None): | |
|
||
def write_kernel_spec(path=None, overrides=None, extra_arguments=None): | ||
"""Write a kernel spec directory to `path` | ||
|
||
If `path` is not specified, a temporary directory is created. | ||
If `overrides` is given, the kernelspec JSON is updated before writing. | ||
|
||
if 'resources' is given, copies resources from non-standard location. | ||
|
||
The path to the kernelspec is always returned. | ||
""" | ||
if path is None: | ||
path = os.path.join(tempfile.mkdtemp(suffix='_kernels'), KERNEL_NAME) | ||
|
||
# stage resources | ||
shutil.copytree(RESOURCES, path) | ||
|
||
# change permission if resources directory is not read-write able | ||
if not os.access(path, os.W_OK | os.R_OK): | ||
# changes permissions only for owner, do not touch group/other | ||
os.chmod(path, os.stat(path).st_mode | 0o700) | ||
for f in os.listdir(path): | ||
file_path = os.path.join(path, f) | ||
os.chmod(file_path, os.stat(file_path).st_mode | 0o600) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the relevant piece of this code has already been merged in #593 (and unfortunately earlier in #377, which was overlooked). I think that we don't need the directory to have the execute bit set, only have it be writeable. We don't need all of the files to be writeable - since those are icons - they just need to be readable - which gets taken care of by copytree just above this section, since if they weren't readable from the RESOURCES directory, that would have failed. I might have overlooked something subtle, though, but either way this would need to be rebased on master, as it conflicts with what ended up making it to ipykernel 5.5.0 in #593. |
||
# write kernel.json | ||
kernel_dict = get_kernel_dict(extra_arguments) | ||
|
||
if overrides: | ||
kernel_dict.update(overrides) | ||
with open(pjoin(path, 'kernel.json'), 'w') as f: | ||
json.dump(kernel_dict, f, indent=1) | ||
|
||
return path | ||
|
||
|
||
def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, display_name=None, | ||
prefix=None, profile=None): | ||
"""Install the IPython kernelspec for Jupyter | ||
|
||
Parameters | ||
---------- | ||
|
||
kernel_spec_manager: KernelSpecManager [optional] | ||
A KernelSpecManager to use for installation. | ||
If none provided, a default instance will be created. | ||
|
@@ -108,7 +118,7 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ | |
|
||
Returns | ||
------- | ||
|
||
The path where the kernelspec was installed. | ||
""" | ||
if kernel_spec_manager is None: | ||
|
@@ -143,12 +153,12 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ | |
class InstallIPythonKernelSpecApp(Application): | ||
"""Dummy app wrapping argparse""" | ||
name = 'ipython-kernel-install' | ||
|
||
def initialize(self, argv=None): | ||
if argv is None: | ||
argv = sys.argv[1:] | ||
self.argv = argv | ||
|
||
def start(self): | ||
import argparse | ||
parser = argparse.ArgumentParser(prog=self.name, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ | |
except ImportError: | ||
import mock # py2 | ||
|
||
import pytest | ||
|
||
from jupyter_core.paths import jupyter_data_dir | ||
|
||
from ipykernel.kernelspec import ( | ||
|
@@ -88,10 +90,41 @@ def test_write_kernel_spec_path(): | |
shutil.rmtree(path) | ||
|
||
|
||
@pytest.mark.skipif(sys.platform == 'win32', | ||
reason="does not run on windows") | ||
def test_write_kernel_spec_permissions(tmp_path): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function should now probably be the main focus of this PR. I believe the implementation from #593 runs on Windows (at least it does for me on Python 3.9.2), so no need to skip that platform. |
||
read_only_resources = os.path.join(tmp_path, "_RESOURCES") | ||
shutil.copytree(RESOURCES, read_only_resources) | ||
|
||
# file used to check that the correct (mocked) resource directory was used | ||
with open(read_only_resources + '/touch', 'w') as f: | ||
pass | ||
|
||
# make copy of resources directory read-only | ||
os.chmod(read_only_resources, 0o500) | ||
for f in os.listdir(read_only_resources): | ||
os.chmod(os.path.join(read_only_resources, f), 0o400) | ||
|
||
with mock.patch('ipykernel.kernelspec.RESOURCES', read_only_resources): | ||
path = write_kernel_spec() | ||
|
||
assert_is_spec(path) | ||
|
||
# if `touch` is missing then the wrong resources directory was copied by | ||
# `write_kernel_spec`, `RESOURCES` mocking didn't work? | ||
assert os.path.isfile(path + '/touch') | ||
|
||
# ensure permissions are not loosened too much, original permission was | ||
# 0o500, so the 'fixed' one should be 0o700, still no rw for group/other | ||
assert os.stat(path).st_mode & 0o77 == 0o00 | ||
|
||
shutil.rmtree(path) | ||
|
||
|
||
def test_install_kernelspec(): | ||
|
||
path = tempfile.mkdtemp() | ||
try: | ||
try: | ||
test = InstallIPythonKernelSpecApp.launch_instance(argv=['--prefix', path]) | ||
assert_is_spec(os.path.join( | ||
path, 'share', 'jupyter', 'kernels', KERNEL_NAME)) | ||
|
@@ -101,21 +134,21 @@ def test_install_kernelspec(): | |
|
||
def test_install_user(): | ||
tmp = tempfile.mkdtemp() | ||
|
||
with mock.patch.dict(os.environ, {'HOME': tmp}): | ||
install(user=True) | ||
data_dir = jupyter_data_dir() | ||
|
||
assert_is_spec(os.path.join(data_dir, 'kernels', KERNEL_NAME)) | ||
|
||
|
||
def test_install(): | ||
system_jupyter_dir = tempfile.mkdtemp() | ||
|
||
with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', | ||
[system_jupyter_dir]): | ||
install() | ||
|
||
assert_is_spec(os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME)) | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be removed, since resources were added, but then removed as a parameter to write_kernel_spec in this PR.