Skip to content

Commit

Permalink
Update blacklist and whitelist keywords (#1367)
Browse files Browse the repository at this point in the history
* Rename `file_blacklist` and `file_whitelist`

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Rename `extension_blacklist` and `extension_whitelist`

* Rename `preheat_blacklist`

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
trungleduc and pre-commit-ci[bot] committed Aug 2, 2023
1 parent cd33c55 commit 72a7e56
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 74 deletions.
12 changes: 6 additions & 6 deletions docs/source/customize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -363,17 +363,17 @@ Serving static files

Unlike JupyterLab or the classic notebook server, ``voila`` does not serve
all files that are present in the directory of the notebook. Only files that
match one of the whitelists and none of the blacklist regular expression are
match one of the allowlist and none of the denylist regular expression are
served by Voilà::

voila mydir --VoilaConfiguration.file_whitelist="['.*']" \
--VoilaConfiguration.file_blacklist="['private.*', '.*\.(ipynb)']"
voila mydir --VoilaConfiguration.file_allowlist="['.*']" \
--VoilaConfiguration.file_denylist="['private.*', '.*\.(ipynb)']"

Which will serve all files, except anything starting with private, or notebook files::

voila mydir --VoilaConfiguration.file_whitelist="['.*\.(png|jpg|gif|svg|mp4|avi|ogg)']"
voila mydir --VoilaConfiguration.file_allowlist="['.*\.(png|jpg|gif|svg|mp4|avi|ogg)']"

Will serve many media files, and also never serve notebook files (which is the default blacklist).
Will serve many media files, and also never serve notebook files (which is the default denylist).

Run scripts
===========
Expand Down Expand Up @@ -466,7 +466,7 @@ Here is an example of settings with explanations for preheating kernel option.
},
"VoilaKernelManager": {
# A list of notebook name or regex patterns to exclude notebooks from using preheat kernel.
"preheat_blacklist": [
"preheat_denylist": [
"notebook-does-not-need-preheat.ipynb",
"^.*foo.*$",
...
Expand Down
4 changes: 2 additions & 2 deletions tests/app/preheat_multiple_notebooks_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ async def test_render_notebook_with_heated_kernel(http_server_client, base_url):
await asyncio.sleep(NOTEBOOK_EXECUTION_TIME + 1)


async def test_render_blacklisted_notebook_with_nornal_kernel(
async def test_render_denylisted_notebook_with_nornal_kernel(
http_server_client, base_url
):
await asyncio.sleep(NUMBER_PREHEATED_KERNEL * NOTEBOOK_EXECUTION_TIME + 1)
time, text = await send_request(
sc=http_server_client, url=f"{base_url}voila/render/blacklisted.ipynb"
sc=http_server_client, url=f"{base_url}voila/render/denylisted.ipynb"
)

assert "hello world" in text
Expand Down
2 changes: 1 addition & 1 deletion tests/configs/preheat/voila.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"preheat_kernel": true
},
"VoilaKernelManager": {
"preheat_blacklist": ["blacklisted.ipynb"],
"preheat_denylist": ["denylisted.ipynb"],
"kernel_pools_config": {
"default": {
"pool_size": 1
Expand Down
File renamed without changes.
3 changes: 3 additions & 0 deletions voila/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from ._version import __version__ # noqa
from .server_extension import _load_jupyter_server_extension # noqa
from .server_extension import load_jupyter_server_extension # noqa
import warnings

warnings.filterwarnings("default", category=DeprecationWarning, module="traitlets")


def _jupyter_nbextension_paths():
Expand Down
8 changes: 4 additions & 4 deletions voila/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@
from .request_info_handler import RequestInfoSocketHandler
from .shutdown_kernel_handler import VoilaShutdownKernelHandler
from .static_file_handler import (
AllowListFileHandler,
MultiStaticFileHandler,
TemplateStaticFileHandler,
WhiteListFileHandler,
)
from .tornado.handler import TornadoVoilaHandler
from .tornado.treehandler import TornadoVoilaTreeHandler
Expand Down Expand Up @@ -701,10 +701,10 @@ def init_handlers(self) -> List:
handlers.append(
(
url_path_join(self.server_url, r"/voila/files/(.*)"),
WhiteListFileHandler,
AllowListFileHandler,
{
"whitelist": self.voila_configuration.file_whitelist,
"blacklist": self.voila_configuration.file_blacklist,
"allowlist": self.voila_configuration.file_allowlist,
"denylist": self.voila_configuration.file_denylist,
"path": self.root_dir,
},
)
Expand Down
60 changes: 47 additions & 13 deletions voila/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
#############################################################################

import traitlets.config
from traitlets import Bool, Dict, Enum, Int, List, Type, Unicode
from traitlets import Bool, Dict, Enum, Int, List, Type, Unicode, validate

from warnings import warn


class VoilaConfiguration(traitlets.config.Configurable):
Expand Down Expand Up @@ -52,30 +54,62 @@ class VoilaConfiguration(traitlets.config.Configurable):
)
strip_sources = Bool(True, config=True, help="Strip sources from rendered html")

file_whitelist = List(
file_allowlist = List(
Unicode(),
[r".*\.(png|jpg|gif|svg)"],
config=True,
help=r"""
List of regular expressions for controlling which static files are served.
All files that are served should at least match 1 whitelist rule, and no blacklist rule
Example: --VoilaConfiguration.file_whitelist="['.*\.(png|jpg|gif|svg)', 'public.*']"
All files that are served should at least match 1 allowlist rule, and no denylist rule
Example: --VoilaConfiguration.file_allowlist="['.*\.(png|jpg|gif|svg)', 'public.*']"
""",
)

file_blacklist = List(
file_whitelist = List(
Unicode(),
[r".*\.(png|jpg|gif|svg)"],
config=True,
help="""Deprecated, use `file_allowlist`""",
)

@validate("file_whitelist")
def _valid_file_whitelist(self, proposal):
warn(
"Deprecated, use VoilaConfiguration.file_allowlist instead.",
DeprecationWarning,
stacklevel=2,
)
return proposal["value"]

file_denylist = List(
Unicode(),
[r".*\.(ipynb|py)"],
config=True,
help=r"""
List of regular expressions for controlling which static files are forbidden to be served.
All files that are served should at least match 1 whitelist rule, and no blacklist rule
Example:
--VoilaConfiguration.file_whitelist="['.*']" # all files
--VoilaConfiguration.file_blacklist="['private.*', '.*\.(ipynb)']" # except files in the private dir and notebook files
List of regular expressions for controlling which static files are forbidden to be served.
All files that are served should at least match 1 allowlist rule, and no denylist rule
Example:
--VoilaConfiguration.file_allowlist="['.*']" # all files
--VoilaConfiguration.file_denylist="['private.*', '.*\.(ipynb)']" # except files in the private dir and notebook files
""",
)

file_blacklist = List(
Unicode(),
[r".*\.(ipynb|py)"],
config=True,
help="""Deprecated, use `file_denylist`""",
)

@validate("file_blacklist")
def _valid_file_blacklist(self, proposal):
warn(
"Deprecated, use VoilaConfiguration.file_denylist instead.",
DeprecationWarning,
stacklevel=2,
)
return proposal["value"]

language_kernel_mapping = Dict(
{},
config=True,
Expand Down Expand Up @@ -141,16 +175,16 @@ class VoilaConfiguration(traitlets.config.Configurable):
""",
)

extension_whitelist = List(
extension_allowlist = List(
None,
allow_none=True,
config=True,
help="""The list of enabled JupyterLab extensions, if `None`, all extensions are loaded.
This setting has higher priority than the `extension_blacklist`
This setting has higher priority than the `extension_denylist`
""",
)

extension_blacklist = List(
extension_denylist = List(
None,
allow_none=True,
config=True,
Expand Down
8 changes: 5 additions & 3 deletions voila/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from tornado.httputil import split_host_and_port
from traitlets.traitlets import Bool

from .configuration import VoilaConfiguration

from ._version import __version__
from .notebook_renderer import NotebookRenderer
from .request_info_handler import RequestInfoSocketHandler
Expand Down Expand Up @@ -68,7 +70,7 @@ def initialize(self, **kwargs):
self.notebook_path = kwargs.pop("notebook_path", []) # should it be []
self.template_paths = kwargs.pop("template_paths", [])
self.traitlet_config = kwargs.pop("config", None)
self.voila_configuration = kwargs["voila_configuration"]
self.voila_configuration: VoilaConfiguration = kwargs["voila_configuration"]
self.prelaunch_hook = kwargs.get("prelaunch_hook", None)
# we want to avoid starting multiple kernels due to template mistakes
self.kernel_started = False
Expand Down Expand Up @@ -198,8 +200,8 @@ async def get_generator(self, path=None):
base_url=self.base_url,
settings=self.settings,
log=self.log,
extension_whitelist=self.voila_configuration.extension_whitelist,
extension_blacklist=self.voila_configuration.extension_blacklist,
extension_allowlist=self.voila_configuration.extension_allowlist,
extension_denylist=self.voila_configuration.extension_denylist,
),
mathjax_config=mathjax_config,
mathjax_url=mathjax_url,
Expand Down
8 changes: 4 additions & 4 deletions voila/server_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from .static_file_handler import (
MultiStaticFileHandler,
TemplateStaticFileHandler,
WhiteListFileHandler,
AllowListFileHandler,
)
from .tornado.treehandler import TornadoVoilaTreeHandler
from .utils import get_data_dir, get_server_root_dir, pjoin
Expand Down Expand Up @@ -110,10 +110,10 @@ def _load_jupyter_server_extension(server_app):
),
(
url_path_join(base_url, r"/voila/files/(.*)"),
WhiteListFileHandler,
AllowListFileHandler,
{
"whitelist": voila_configuration.file_whitelist,
"blacklist": voila_configuration.file_blacklist,
"allowlist": voila_configuration.file_allowlist,
"denylist": voila_configuration.file_denylist,
"path": os.path.expanduser(get_server_root_dir(web_app.settings)),
},
),
Expand Down
20 changes: 10 additions & 10 deletions voila/static_file_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,19 @@ def get_absolute_path(self, root, path):
return abspath


class WhiteListFileHandler(tornado.web.StaticFileHandler):
def initialize(self, whitelist=[], blacklist=[], **kwargs):
self.whitelist = whitelist
self.blacklist = blacklist
class AllowListFileHandler(tornado.web.StaticFileHandler):
def initialize(self, allowlist=[], denylist=[], **kwargs):
self.allowlist = allowlist
self.denylist = denylist
super().initialize(**kwargs)

def get_absolute_path(self, root, path):
# StaticFileHandler.get always calls this method first, so we use this as the
# place to check the path. Note that now the path separator is os dependent (\\ on windows)
whitelisted = any(re.fullmatch(pattern, path) for pattern in self.whitelist)
blacklisted = any(re.fullmatch(pattern, path) for pattern in self.blacklist)
if not whitelisted:
raise tornado.web.HTTPError(403, "File not whitelisted")
if blacklisted:
raise tornado.web.HTTPError(403, "File blacklisted")
allowlisted = any(re.fullmatch(pattern, path) for pattern in self.allowlist)
denylisted = any(re.fullmatch(pattern, path) for pattern in self.denylist)
if not allowlisted:
raise tornado.web.HTTPError(403, "File not allowlisted")
if denylisted:
raise tornado.web.HTTPError(403, "File denylisted")
return super().get_absolute_path(root, path)
4 changes: 2 additions & 2 deletions voila/tornado/treehandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ def allowed_content(content):
base_url=self.base_url,
settings=self.settings,
log=self.log,
extension_whitelist=self.voila_configuration.extension_whitelist,
extension_blacklist=self.voila_configuration.extension_blacklist,
extension_allowlist=self.voila_configuration.extension_allowlist,
extension_denylist=self.voila_configuration.extension_denylist,
)
page_config["jupyterLabTheme"] = theme_arg

Expand Down
46 changes: 23 additions & 23 deletions voila/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ def get_page_config(
base_url,
settings,
log,
extension_whitelist: List[str] = [],
extension_blacklist: List[str] = [],
extension_allowlist: List[str] = [],
extension_denylist: List[str] = [],
):
page_config = {
"appVersion": __version__,
Expand Down Expand Up @@ -110,38 +110,38 @@ def get_page_config(
"@voila-dashboards/jupyterlab-preview",
"@jupyter/collaboration-extension",
]
must_have_extensions = ["@jupyter-widgets/jupyterlab-manager"]
required_extensions = ["@jupyter-widgets/jupyterlab-manager"]
federated_extensions = deepcopy(page_config["federated_extensions"])

page_config["federated_extensions"] = filter_extension(
federated_extensions=federated_extensions,
disabled_extensions=disabled_extensions,
must_have_extensions=must_have_extensions,
extension_whitelist=extension_whitelist,
extension_blacklist=extension_blacklist,
required_extensions=required_extensions,
extension_allowlist=extension_allowlist,
extension_denylist=extension_denylist,
)
return page_config


def filter_extension(
federated_extensions: List[Dict],
disabled_extensions: List[str] = [],
must_have_extensions: List[str] = [],
extension_whitelist: List[str] = [],
extension_blacklist: List[str] = [],
required_extensions: List[str] = [],
extension_allowlist: List[str] = [],
extension_denylist: List[str] = [],
) -> List[Dict]:
"""Create a list of extension to be loaded from available extensions and the
black/white list configuration.
allow/deny list configuration.
Args:
- federated_extensions (List[Dict]): List of available extension
- disabled_extensions (List[str], optional): List of extension disabled by default.
Defaults to [].
- must_have_extensions (List[str], optional): List of extension must be enabled.
- required_extensions (List[str], optional): List of required extensions.
Defaults to [].
- extension_whitelist (List[str], optional): The white listed extensions.
- extension_allowlist (List[str], optional): The allowlisted extensions.
Defaults to [].
- extension_blacklist (List[str], optional): The black listed extensions.
- extension_denylist (List[str], optional): The denylisted extensions.
Defaults to [].
Returns:
Expand All @@ -150,31 +150,31 @@ def filter_extension(
filtered_extensions = [
x for x in federated_extensions if x["name"] not in disabled_extensions
]
if len(extension_blacklist) == 0:
if len(extension_whitelist) == 0:
# No white and black list, return all
if len(extension_denylist) == 0:
if len(extension_allowlist) == 0:
# No allow and deny list, return all
return filtered_extensions

# White list is not empty, return white listed only
# Allow list is not empty, return allow listed only
return [
x
for x in filtered_extensions
if x["name"] in must_have_extensions or x["name"] in extension_whitelist
if x["name"] in required_extensions or x["name"] in extension_allowlist
]

if len(extension_whitelist) == 0:
# No white list, return non black listed only
if len(extension_allowlist) == 0:
# No allow list, return non deny listed only
return [
x
for x in filtered_extensions
if x["name"] in must_have_extensions or x["name"] not in extension_blacklist
if x["name"] in required_extensions or x["name"] not in extension_denylist
]

# Have both black and white list, use only white list
# Have both allow and deny list, use only allow list
return [
x
for x in filtered_extensions
if x["name"] in must_have_extensions or x["name"] in extension_whitelist
if x["name"] in required_extensions or x["name"] in extension_allowlist
]


Expand Down
Loading

0 comments on commit 72a7e56

Please sign in to comment.