Skip to content

Use jupyter server as base provider #384

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

Merged
merged 32 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
acf6175
Initial pass, imports and async/await
kevin-bates Mar 2, 2023
b8ebe32
More async/await fixups
kevin-bates Mar 2, 2023
97caf96
Refactor initialization to address async functionality
kevin-bates Mar 7, 2023
19be235
Drop Python 3.7, add 3.10 & 3.11
kevin-bates Mar 7, 2023
04958ca
Switch from nose to pytest as the driver
kevin-bates Mar 7, 2023
70962d0
Update test app initialization
kevin-bates Mar 7, 2023
1ad9433
Remove nose ref from test
kevin-bates Mar 7, 2023
bfc5e53
Begin transition to pytest
kevin-bates Mar 11, 2023
2c8b72f
Transition to async/await
kevin-bates Mar 11, 2023
306cbbd
Complete pytest transition
kevin-bates Mar 15, 2023
1598020
Move conftest.py to top level due to pytest deprecation
kevin-bates Mar 15, 2023
bd5fcc4
Convert parser tests
kevin-bates Mar 15, 2023
80eeb17
Require pytest-jupyter
kevin-bates Mar 15, 2023
4e8f479
Cap jupyter_server until identity_provider attribute can be worked out
kevin-bates Mar 15, 2023
f22dce4
Update test requirements
kevin-bates Mar 15, 2023
14d765f
Add coverage
kevin-bates Mar 15, 2023
a9bc445
Remove troubleshooting logging
kevin-bates Mar 15, 2023
744d83e
Use validated codecov.yml
kevin-bates Mar 16, 2023
952c2d9
Add session api tests, fix bug in sessionmanager
kevin-bates Mar 17, 2023
49d2be9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 17, 2023
b54ece9
Add codecov token to actions workflow
kevin-bates Mar 17, 2023
1792a7a
Improve ssl_options test, run init_signal to improve coverage
kevin-bates Mar 17, 2023
a315d77
Skip kernel_info_reply responses when validating seeding
kevin-bates Mar 17, 2023
f59fa39
Remove redundant kernel seed test
kevin-bates Mar 17, 2023
2ef0826
Adjust codecov config
kevin-bates Mar 17, 2023
3a7e9b4
Fix concurrency test to wait for completion
kevin-bates Mar 20, 2023
050f988
Add GatewayIdentityProvider, get tests working
kevin-bates Mar 29, 2023
1292aff
Put a floor on jupyter_server at 2.0
kevin-bates Mar 29, 2023
9f5039c
Set jupyter_server floor, take 2
kevin-bates Mar 29, 2023
43975c5
Default to anonymous identity provider with token authentication
Zsailer Jan 2, 2024
fcd7979
use jupyter server 2.12.2 and above
Zsailer Jan 4, 2024
414cfe2
root_dir defaults to current working directorY
Zsailer Jan 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ jobs:
strategy:
matrix:
python:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"

runs-on: ubuntu-latest

Expand Down Expand Up @@ -70,11 +71,12 @@ jobs:
run: jupyter kernelgateway --help

- name: Run tests
run: nosetests --process-restartworker --with-coverage --cover-package=kernel_gateway
run: pytest -vv -W default --cov kernel_gateway --cov-branch --cov-report term-missing:skip-covered
env:
ASYNC_TEST_TIMEOUT: 10

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ sdist: ## Make a dist/*.tar.gz source distribution
test: TEST?=
test: ## Make a python3 test run
ifeq ($(TEST),)
$(SA) $(ENV) && nosetests
$(SA) $(ENV) && pytest -vv
else
# e.g., make test TEST="test_gatewayapp.TestGatewayAppConfig"
$(SA) $(ENV) && nosetests kernel_gateway.tests.$(TEST)
# e.g., make test TEST="test_gatewayapp.py::TestGatewayAppConfig"
$(SA) $(ENV) && pytest -vv kernel_gateway/tests/$(TEST)
endif

release: POST_SDIST=register upload
Expand Down
10 changes: 10 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
coverage:
status:
project:
default:
target: auto
threshold: 5%
patch:
default:
target: 50%
range: 80..100
104 changes: 104 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import os
import logging
import pytest
from binascii import hexlify
from traitlets.config import Config
from kernel_gateway.gatewayapp import KernelGatewayApp

pytest_plugins = ["pytest_jupyter.jupyter_core", "pytest_jupyter.jupyter_server"]


@pytest.fixture(scope="function")
def jp_configurable_serverapp(
jp_nbconvert_templates, # this fixture must precede jp_environ
jp_environ,
jp_server_config,
jp_argv,
jp_http_port,
jp_base_url,
tmp_path,
jp_root_dir,
jp_logging_stream,
jp_asyncio_loop,
io_loop,
):
"""Starts a Jupyter Server instance based on
the provided configuration values.
The fixture is a factory; it can be called like
a function inside a unit test. Here's a basic
example of how use this fixture:

.. code-block:: python

def my_test(jp_configurable_serverapp):
app = jp_configurable_serverapp(...)
...
"""
KernelGatewayApp.clear_instance()

def _configurable_serverapp(
config=jp_server_config,
base_url=jp_base_url,
argv=jp_argv,
http_port=jp_http_port,
**kwargs,
):
c = Config(config)

if "auth_token" not in c.KernelGatewayApp and not c.IdentityProvider.token:
default_token = hexlify(os.urandom(4)).decode("ascii")
c.IdentityProvider.token = default_token

app = KernelGatewayApp.instance(
# Set the log level to debug for testing purposes
log_level="DEBUG",
port=http_port,
port_retries=0,
base_url=base_url,
config=c,
**kwargs,
)
app.log.propagate = True
app.log.handlers = []
# Initialize app without httpserver
if jp_asyncio_loop.is_running():
app.initialize(argv=argv, new_httpserver=False)
else:

async def initialize_app():
app.initialize(argv=argv, new_httpserver=False)

jp_asyncio_loop.run_until_complete(initialize_app())
# Reroute all logging StreamHandlers away from stdin/stdout since pytest hijacks
# these streams and closes them at unfortunate times.
stream_handlers = [h for h in app.log.handlers if isinstance(h, logging.StreamHandler)]
for handler in stream_handlers:
handler.setStream(jp_logging_stream)
app.log.propagate = True
app.log.handlers = []
app.start_app()
return app

return _configurable_serverapp


@pytest.fixture(autouse=True)
def jp_server_cleanup(jp_asyncio_loop):
yield
app: KernelGatewayApp = KernelGatewayApp.instance()
try:
jp_asyncio_loop.run_until_complete(app.async_shutdown())
except (RuntimeError, SystemExit) as e:
print("ignoring cleanup error", e)
if hasattr(app, "kernel_manager"):
app.kernel_manager.context.destroy()
KernelGatewayApp.clear_instance()


@pytest.fixture
def jp_auth_header(jp_serverapp):
"""Configures an authorization header using the token from the serverapp fixture."""
return {"Authorization": f"token {jp_serverapp.identity_provider.token}"}
2 changes: 1 addition & 1 deletion docs/source/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ The Jupyter Kernel Gateway has the following features:
* Generation of [Swagger specs](http://swagger.io/introducing-the-open-api-initiative/)
for notebook-defined API in `notebook-http` mode
* A CLI for launching the kernel gateway: `jupyter kernelgateway OPTIONS`
* A Python 2.7 and 3.3+ compatible implementation
* A Python 3.8+ compatible implementation
58 changes: 58 additions & 0 deletions kernel_gateway/auth/identity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
"""Gateway Identity Provider interface

This defines the _authentication_ layer of Jupyter Server,
to be used in combination with Authorizer for _authorization_.
"""
from traitlets import default
from tornado import web

from jupyter_server.auth.identity import IdentityProvider, User
from jupyter_server.base.handlers import JupyterHandler


class GatewayIdentityProvider(IdentityProvider):
"""
Interface for providing identity management and authentication for a Gateway server.
"""

@default("token")
def _token_default(self):
return self.parent.auth_token

@property
def auth_enabled(self):
if not self.token:
return False
return True

def should_check_origin(self, handler: JupyterHandler) -> bool:
"""Should the Handler check for CORS origin validation?

Origin check should be skipped for token-authenticated requests.

Returns:
- True, if Handler must check for valid CORS origin.
- False, if Handler should skip origin check since requests are token-authenticated.
"""
# Always check the origin unless operator configured gateway to allow any
return handler.settings["kg_allow_origin"] != "*"

def generate_anonymous_user(self, handler: web.RequestHandler) -> User:
"""Generate a random anonymous user.

For use when a single shared token is used,
but does not identify a user.
"""
name = display_name = f"Anonymous"
initials = "An"
color = None
return User(name.lower(), name, display_name, initials, None, color)

def is_token_authenticated(self, handler: web.RequestHandler) -> bool:
"""The default authentication flow of Gateway is token auth.

The only other option is no auth
"""
return True
7 changes: 5 additions & 2 deletions kernel_gateway/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
"""Tornado handlers for the base of the API."""

from tornado import web
import notebook.base.handlers as notebook_handlers
import jupyter_server.base.handlers as server_handlers
from ..mixins import TokenAuthorizationMixin, CORSMixin, JSONErrorsMixin


class APIVersionHandler(TokenAuthorizationMixin,
CORSMixin,
JSONErrorsMixin,
notebook_handlers.APIVersionHandler):
server_handlers.APIVersionHandler):
"""Extends the notebook server base API handler with token auth, CORS, and
JSON errors.
"""
pass


class NotFoundHandler(JSONErrorsMixin, web.RequestHandler):
"""Catches all requests and responds with 404 JSON messages.

Expand All @@ -28,6 +30,7 @@ class NotFoundHandler(JSONErrorsMixin, web.RequestHandler):
def prepare(self):
raise web.HTTPError(404)


default_handlers = [
(r'/api', APIVersionHandler),
(r'/(.*)', NotFoundHandler)
Expand Down
Loading