Skip to content

Commit 1a743bb

Browse files
committed
Replace secure_write, is_hidden, exists with jupyter_core's
1 parent add9743 commit 1a743bb

File tree

6 files changed

+14
-277
lines changed

6 files changed

+14
-277
lines changed

jupyter_server/base/handlers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
from ipython_genutils.path import filefind
2929
from ipython_genutils.py3compat import string_types
3030

31+
from jupyter_core.paths import is_hidden
3132
import jupyter_server
3233
from jupyter_server._tz import utcnow
3334
from jupyter_server.i18n import combine_translations
34-
from jupyter_server.utils import ensure_async, is_hidden, url_path_join, url_is_absolute, url_escape
35+
from jupyter_server.utils import ensure_async, url_path_join, url_is_absolute, url_escape
3536
from jupyter_server.services.security import csp_report_uri
3637

3738
#-----------------------------------------------------------------------------

jupyter_server/serverapp.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@
77
from __future__ import absolute_import, print_function
88

99
import jupyter_server
10-
import asyncio
1110
import binascii
1211
import datetime
1312
import errno
1413
import gettext
1514
import hashlib
1615
import hmac
17-
import importlib
1816
import io
1917
import ipaddress
2018
import json
@@ -34,7 +32,6 @@
3432
import webbrowser
3533
import urllib
3634

37-
from types import ModuleType
3835
from base64 import encodebytes
3936
try:
4037
import resource
@@ -44,8 +41,9 @@
4441

4542
from jinja2 import Environment, FileSystemLoader
4643

44+
from jupyter_core.paths import secure_write
4745
from jupyter_server.transutils import trans, _
48-
from jupyter_server.utils import secure_write, run_sync
46+
from jupyter_server.utils import run_sync
4947

5048
# the minimum viable tornado version: needs to be kept in sync with setup.py
5149
MIN_TORNADO = (6, 1, 0)

jupyter_server/services/contents/filemanager.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,13 @@
1919
from .filecheckpoints import AsyncFileCheckpoints, FileCheckpoints
2020
from .fileio import AsyncFileManagerMixin, FileManagerMixin
2121
from .manager import AsyncContentsManager, ContentsManager
22-
from ...utils import exists
2322

2423
from ipython_genutils.importstring import import_item
2524
from traitlets import Any, Unicode, Bool, TraitError, observe, default, validate
2625
from ipython_genutils.py3compat import getcwd, string_types
2726

27+
from jupyter_core.paths import exists, is_hidden, is_file_hidden
2828
from jupyter_server import _tz as tz
29-
from jupyter_server.utils import (
30-
is_hidden, is_file_hidden,
31-
to_api_path,
32-
)
3329
from jupyter_server.base.handlers import AuthenticatedFileHandler
3430
from jupyter_server.transutils import _
3531

jupyter_server/services/kernels/kernelmanager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818

1919
from jupyter_client.session import Session
2020
from jupyter_client.multikernelmanager import MultiKernelManager, AsyncMultiKernelManager
21+
from jupyter_core.paths import exists
2122
from traitlets import (Any, Bool, Dict, List, Unicode, TraitError, Integer,
2223
Float, Instance, default, validate
2324
)
2425

25-
from jupyter_server.utils import to_os_path, exists, ensure_async, run_sync
26+
from jupyter_server.utils import to_os_path, ensure_async
2627
from jupyter_server._tz import utcnow, isoformat
2728
from ipython_genutils.py3compat import getcwd
2829

jupyter_server/utils.py

Lines changed: 6 additions & 220 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,17 @@
66
from __future__ import print_function
77

88
import asyncio
9-
import concurrent.futures
10-
import ctypes
119
import errno
1210
import inspect
1311
import os
14-
import stat
1512
import sys
1613
from distutils.version import LooseVersion
17-
from contextlib import contextmanager
18-
1914

2015
from urllib.parse import quote, unquote, urlparse, urljoin
2116
from urllib.request import pathname2url
2217

23-
24-
# tornado.concurrent.Future is asyncio.Future
25-
# in tornado >=5 with Python 3
26-
from tornado.concurrent import Future as TornadoFuture
27-
from tornado import gen
2818
from ipython_genutils import py3compat
2919

30-
# UF_HIDDEN is a stat flag not defined in the stat module.
31-
# It is used by BSD to indicate hidden files.
32-
UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
33-
34-
35-
def exists(path):
36-
"""Replacement for `os.path.exists` which works for host mapped volumes
37-
on Windows containers
38-
"""
39-
try:
40-
os.lstat(path)
41-
except OSError:
42-
return False
43-
return True
44-
4520

4621
def url_path_join(*pieces):
4722
"""Join components of url into a relative url
@@ -58,10 +33,12 @@ def url_path_join(*pieces):
5833
if result == '//': result = '/'
5934
return result
6035

36+
6137
def url_is_absolute(url):
6238
"""Determine whether a given URL is absolute"""
6339
return urlparse(url).path.startswith("/")
6440

41+
6542
def path2url(path):
6643
"""Convert a local file path to a URL"""
6744
pieces = [ quote(p) for p in path.split(os.sep) ]
@@ -71,12 +48,14 @@ def path2url(path):
7148
url = url_path_join(*pieces)
7249
return url
7350

51+
7452
def url2path(url):
7553
"""Convert a URL to a local file path"""
7654
pieces = [ unquote(p) for p in url.split('/') ]
7755
path = os.path.join(*pieces)
7856
return path
7957

58+
8059
def url_escape(path):
8160
"""Escape special characters in a URL path
8261
@@ -85,6 +64,7 @@ def url_escape(path):
8564
parts = py3compat.unicode_to_str(path, encoding='utf8').split('/')
8665
return u'/'.join([quote(p) for p in parts])
8766

67+
8868
def url_unescape(path):
8969
"""Unescape special characters in a URL path
9070
@@ -96,201 +76,6 @@ def url_unescape(path):
9676
])
9777

9878

99-
def is_file_hidden_win(abs_path, stat_res=None):
100-
"""Is a file hidden?
101-
102-
This only checks the file itself; it should be called in combination with
103-
checking the directory containing the file.
104-
105-
Use is_hidden() instead to check the file and its parent directories.
106-
107-
Parameters
108-
----------
109-
abs_path : unicode
110-
The absolute path to check.
111-
stat_res : os.stat_result, optional
112-
Ignored on Windows, exists for compatibility with POSIX version of the
113-
function.
114-
"""
115-
if os.path.basename(abs_path).startswith('.'):
116-
return True
117-
118-
win32_FILE_ATTRIBUTE_HIDDEN = 0x02
119-
try:
120-
attrs = ctypes.windll.kernel32.GetFileAttributesW(
121-
py3compat.cast_unicode(abs_path)
122-
)
123-
except AttributeError:
124-
pass
125-
else:
126-
if attrs > 0 and attrs & win32_FILE_ATTRIBUTE_HIDDEN:
127-
return True
128-
129-
return False
130-
131-
def is_file_hidden_posix(abs_path, stat_res=None):
132-
"""Is a file hidden?
133-
134-
This only checks the file itself; it should be called in combination with
135-
checking the directory containing the file.
136-
137-
Use is_hidden() instead to check the file and its parent directories.
138-
139-
Parameters
140-
----------
141-
abs_path : unicode
142-
The absolute path to check.
143-
stat_res : os.stat_result, optional
144-
The result of calling stat() on abs_path. If not passed, this function
145-
will call stat() internally.
146-
"""
147-
if os.path.basename(abs_path).startswith('.'):
148-
return True
149-
150-
if stat_res is None or stat.S_ISLNK(stat_res.st_mode):
151-
try:
152-
stat_res = os.stat(abs_path)
153-
except OSError as e:
154-
if e.errno == errno.ENOENT:
155-
return False
156-
raise
157-
158-
# check that dirs can be listed
159-
if stat.S_ISDIR(stat_res.st_mode):
160-
# use x-access, not actual listing, in case of slow/large listings
161-
if not os.access(abs_path, os.X_OK | os.R_OK):
162-
return True
163-
164-
# check UF_HIDDEN
165-
if getattr(stat_res, 'st_flags', 0) & UF_HIDDEN:
166-
return True
167-
168-
return False
169-
170-
if sys.platform == 'win32':
171-
is_file_hidden = is_file_hidden_win
172-
else:
173-
is_file_hidden = is_file_hidden_posix
174-
175-
def is_hidden(abs_path, abs_root=''):
176-
"""Is a file hidden or contained in a hidden directory?
177-
178-
This will start with the rightmost path element and work backwards to the
179-
given root to see if a path is hidden or in a hidden directory. Hidden is
180-
determined by either name starting with '.' or the UF_HIDDEN flag as
181-
reported by stat.
182-
183-
If abs_path is the same directory as abs_root, it will be visible even if
184-
that is a hidden folder. This only checks the visibility of files
185-
and directories *within* abs_root.
186-
187-
Parameters
188-
----------
189-
abs_path : unicode
190-
The absolute path to check for hidden directories.
191-
abs_root : unicode
192-
The absolute path of the root directory in which hidden directories
193-
should be checked for.
194-
"""
195-
if os.path.normpath(abs_path) == os.path.normpath(abs_root):
196-
return False
197-
198-
if is_file_hidden(abs_path):
199-
return True
200-
201-
if not abs_root:
202-
abs_root = abs_path.split(os.sep, 1)[0] + os.sep
203-
inside_root = abs_path[len(abs_root):]
204-
if any(part.startswith('.') for part in inside_root.split(os.sep)):
205-
return True
206-
207-
# check UF_HIDDEN on any location up to root.
208-
# is_file_hidden() already checked the file, so start from its parent dir
209-
path = os.path.dirname(abs_path)
210-
while path and path.startswith(abs_root) and path != abs_root:
211-
if not exists(path):
212-
path = os.path.dirname(path)
213-
continue
214-
try:
215-
# may fail on Windows junctions
216-
st = os.lstat(path)
217-
except OSError:
218-
return True
219-
if getattr(st, 'st_flags', 0) & UF_HIDDEN:
220-
return True
221-
path = os.path.dirname(path)
222-
223-
return False
224-
225-
# TODO: Move to jupyter_core
226-
def win32_restrict_file_to_user(fname):
227-
"""Secure a windows file to read-only access for the user.
228-
Follows guidance from win32 library creator:
229-
http://timgolden.me.uk/python/win32_how_do_i/add-security-to-a-file.html
230-
231-
This method should be executed against an already generated file which
232-
has no secrets written to it yet.
233-
234-
Parameters
235-
----------
236-
237-
fname : unicode
238-
The path to the file to secure
239-
"""
240-
import win32api
241-
import win32security
242-
import ntsecuritycon as con
243-
244-
# everyone, _domain, _type = win32security.LookupAccountName("", "Everyone")
245-
admins = win32security.CreateWellKnownSid(win32security.WinBuiltinAdministratorsSid)
246-
user, _domain, _type = win32security.LookupAccountName("", win32api.GetUserName())
247-
248-
sd = win32security.GetFileSecurity(fname, win32security.DACL_SECURITY_INFORMATION)
249-
250-
dacl = win32security.ACL()
251-
# dacl.AddAccessAllowedAce(win32security.ACL_REVISION, con.FILE_ALL_ACCESS, everyone)
252-
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, con.FILE_GENERIC_READ | con.FILE_GENERIC_WRITE, user)
253-
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, con.FILE_ALL_ACCESS, admins)
254-
255-
sd.SetSecurityDescriptorDacl(1, dacl, 0)
256-
win32security.SetFileSecurity(fname, win32security.DACL_SECURITY_INFORMATION, sd)
257-
258-
# TODO: Move to jupyter_core
259-
@contextmanager
260-
def secure_write(fname, binary=False):
261-
"""Opens a file in the most restricted pattern available for
262-
writing content. This limits the file mode to `600` and yields
263-
the resulting opened filed handle.
264-
265-
Parameters
266-
----------
267-
268-
fname : unicode
269-
The path to the file to write
270-
"""
271-
mode = 'wb' if binary else 'w'
272-
open_flag = os.O_CREAT | os.O_WRONLY | os.O_TRUNC
273-
try:
274-
os.remove(fname)
275-
except (IOError, OSError):
276-
# Skip any issues with the file not existing
277-
pass
278-
279-
if os.name == 'nt':
280-
# Python on windows does not respect the group and public bits for chmod, so we need
281-
# to take additional steps to secure the contents.
282-
# Touch file pre-emptively to avoid editing permissions in open files in Windows
283-
fd = os.open(fname, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600)
284-
os.close(fd)
285-
open_flag = os.O_WRONLY | os.O_TRUNC
286-
win32_restrict_file_to_user(fname)
287-
288-
with os.fdopen(os.open(fname, open_flag, 0o600), mode) as f:
289-
if os.name != 'nt':
290-
# Enforce that the file got the requested permissions before writing
291-
assert '0600' == oct(stat.S_IMODE(os.stat(fname).st_mode)).replace('0o', '0')
292-
yield f
293-
29479
def samefile_simple(path, other_path):
29580
"""
29681
Fill in for os.path.samefile when it is unavailable (Windows+py2).
@@ -328,6 +113,7 @@ def to_os_path(path, root=''):
328113
path = os.path.join(root, *parts)
329114
return path
330115

116+
331117
def to_api_path(os_path, root=''):
332118
"""Convert a filesystem path to an API path
333119

0 commit comments

Comments
 (0)