From a3e3f24d2d24e2ce1a6f1b5c49fca9f8936fd3b9 Mon Sep 17 00:00:00 2001
From: John Paton
Date: Fri, 14 Feb 2020 16:35:48 +0100
Subject: [PATCH 01/20] Add favicon to the base page template
This was missing before. Giving it its own named block will let users customize it if they wish.
---
share/jupyterhub/templates/page.html | 3 +++
1 file changed, 3 insertions(+)
diff --git a/share/jupyterhub/templates/page.html b/share/jupyterhub/templates/page.html
index 50f910c5..b40be095 100644
--- a/share/jupyterhub/templates/page.html
+++ b/share/jupyterhub/templates/page.html
@@ -34,6 +34,9 @@
{% block stylesheet %}
{% endblock %}
+ {% block favicon %}
+
+ {% endblock %}
{% block scripts %}
From a999ac8f073e2ff6b6e084a05eb40dd3993292c8 Mon Sep 17 00:00:00 2001
From: JohnPaton
Date: Fri, 14 Feb 2020 16:51:27 +0100
Subject: [PATCH 02/20] Use only rel="icon"
This is the officially recommended method from MDN
https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML
---
share/jupyterhub/templates/page.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/share/jupyterhub/templates/page.html b/share/jupyterhub/templates/page.html
index b40be095..bc701ce4 100644
--- a/share/jupyterhub/templates/page.html
+++ b/share/jupyterhub/templates/page.html
@@ -35,7 +35,7 @@
{% endblock %}
{% block favicon %}
-
+
{% endblock %}
{% block scripts %}
From 79a51dfdce028e83d2921fdf29053307bdeb896d Mon Sep 17 00:00:00 2001
From: Min RK
Date: Tue, 18 Feb 2020 17:10:19 +0100
Subject: [PATCH 03/20] make init_spawners check O(running servers) not O(total
users)
query on Server objects instead of User objects
avoids lots of ORM work on startup since there are typically a small number of running servers
relative to the total number of users
this also means that the users dict is not fully populated. Is that okay? I hope so.
---
jupyterhub/app.py | 39 ++++++++++++++++++++++++---------------
jupyterhub/orm.py | 4 ++--
2 files changed, 26 insertions(+), 17 deletions(-)
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
index 0bd57de7..b0cc694a 100644
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -1670,9 +1670,12 @@ class JupyterHub(Application):
# This lets whitelist be used to set up initial list,
# but changes to the whitelist can occur in the database,
# and persist across sessions.
+ total_users = 0
for user in db.query(orm.User):
try:
- await maybe_future(self.authenticator.add_user(user))
+ f = self.authenticator.add_user(user)
+ if f:
+ await maybe_future()
except Exception:
self.log.exception("Error adding user %s already in db", user.name)
if self.authenticator.delete_invalid_users:
@@ -1694,6 +1697,7 @@ class JupyterHub(Application):
)
)
else:
+ total_users += 1
# handle database upgrades where user.created is undefined.
# we don't want to allow user.created to be undefined,
# so initialize it to last_activity (if defined) or now.
@@ -1705,6 +1709,8 @@ class JupyterHub(Application):
# From this point on, any user changes should be done simultaneously
# to the whitelist set and user db, unless the whitelist is empty (all users allowed).
+ TOTAL_USERS.set(total_users)
+
async def init_groups(self):
"""Load predefined groups into the database"""
db = self.db
@@ -2005,21 +2011,24 @@ class JupyterHub(Application):
spawner._check_pending = False
# parallelize checks for running Spawners
+ # run query on extant Server objects
+ # so this is O(running servers) not O(total users)
check_futures = []
- for orm_user in db.query(orm.User):
- user = self.users[orm_user]
- self.log.debug("Loading state for %s from db", user.name)
- for name, orm_spawner in user.orm_spawners.items():
- if orm_spawner.server is not None:
- # spawner should be running
- # instantiate Spawner wrapper and check if it's still alive
- spawner = user.spawners[name]
- # signal that check is pending to avoid race conditions
- spawner._check_pending = True
- f = asyncio.ensure_future(check_spawner(user, name, spawner))
- check_futures.append(f)
-
- TOTAL_USERS.set(len(self.users))
+ for orm_server in db.query(orm.Server):
+ orm_spawners = orm_server.spawner
+ if not orm_spawners:
+ continue
+ orm_spawner = orm_spawners[0]
+ orm_user = orm_spawner.user
+ # instantiate Spawner wrapper and check if it's still alive
+ # spawner should be running
+ user = self.users[orm_spawner.user]
+ spawner = user.spawners[orm_spawner.name]
+ self.log.debug("Loading state for %s from db", spawner._log_name)
+ # signal that check is pending to avoid race conditions
+ spawner._check_pending = True
+ f = asyncio.ensure_future(check_spawner(user, spawner.name, spawner))
+ check_futures.append(f)
# it's important that we get here before the first await
# so that we know all spawners are instantiated and in the check-pending state
diff --git a/jupyterhub/orm.py b/jupyterhub/orm.py
index 3198a046..4e151092 100644
--- a/jupyterhub/orm.py
+++ b/jupyterhub/orm.py
@@ -230,7 +230,7 @@ class Spawner(Base):
user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'))
server_id = Column(Integer, ForeignKey('servers.id', ondelete='SET NULL'))
- server = relationship(Server, cascade="all")
+ server = relationship(Server, backref='spawner', cascade="all")
state = Column(JSONDict)
name = Column(Unicode(255))
@@ -282,7 +282,7 @@ class Service(Base):
# service-specific interface
_server_id = Column(Integer, ForeignKey('servers.id', ondelete='SET NULL'))
- server = relationship(Server, cascade='all')
+ server = relationship(Server, backref='service', cascade='all')
pid = Column(Integer)
def new_api_token(self, token=None, **kwargs):
From 13313abb37325aacb2f89f0ce8c3ea61fb53430b Mon Sep 17 00:00:00 2001
From: "Bruno P. Kinoshita"
Date: Wed, 19 Feb 2020 10:46:49 +1300
Subject: [PATCH 04/20] Fix link to SSL encryption from troubleshooting page
---
docs/source/troubleshooting.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/source/troubleshooting.md b/docs/source/troubleshooting.md
index 617a5f44..dc222942 100644
--- a/docs/source/troubleshooting.md
+++ b/docs/source/troubleshooting.md
@@ -269,7 +269,7 @@ where `ssl_cert` is example-chained.crt and ssl_key to your private key.
Then restart JupyterHub.
-See also [JupyterHub SSL encryption](getting-started.md#ssl-encryption).
+See also [JupyterHub SSL encryption](./getting-started/security-basics.html#ssl-encryption).
### Install JupyterHub without a network connection
From dec324475899767babb4c82199e4b7cc1a35b2cf Mon Sep 17 00:00:00 2001
From: "Bruno P. Kinoshita"
Date: Wed, 19 Feb 2020 12:39:20 +1300
Subject: [PATCH 05/20] Use fixed commit plus line number in github link
---
docs/source/reference/services.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/source/reference/services.md b/docs/source/reference/services.md
index 7686d4a4..ec446c8e 100644
--- a/docs/source/reference/services.md
+++ b/docs/source/reference/services.md
@@ -360,7 +360,7 @@ and taking note of the following process:
An example of using an Externally-Managed Service and authentication is
in [nbviewer README][nbviewer example] section on securing the notebook viewer,
-and an example of its configuration is found [here](https://github.com/jupyter/nbviewer/blob/master/nbviewer/providers/base.py#L94).
+and an example of its configuration is found [here](https://github.com/jupyter/nbviewer/blob/ed942b10a52b6259099e2dd687930871dc8aac22/nbviewer/providers/base.py#L95).
nbviewer can also be run as a Hub-Managed Service as described [nbviewer README][nbviewer example]
section on securing the notebook viewer.
From 31c0788bd964fff6178e4a88e7e1f955071a0407 Mon Sep 17 00:00:00 2001
From: "Bruno P. Kinoshita"
Date: Wed, 19 Feb 2020 12:56:02 +1300
Subject: [PATCH 06/20] Move cookies to the end of the list (ssl, proxy, and
then cookies)
---
.../getting-started/security-basics.rst | 77 +++++++++----------
1 file changed, 38 insertions(+), 39 deletions(-)
diff --git a/docs/source/getting-started/security-basics.rst b/docs/source/getting-started/security-basics.rst
index 80996555..7661cfd1 100644
--- a/docs/source/getting-started/security-basics.rst
+++ b/docs/source/getting-started/security-basics.rst
@@ -80,6 +80,44 @@ To achieve this, simply omit the configuration settings
``c.JupyterHub.ssl_key`` and ``c.JupyterHub.ssl_cert``
(setting them to ``None`` does not have the same effect, and is an error).
+.. _authentication-token:
+
+Proxy authentication token
+--------------------------
+
+The Hub authenticates its requests to the Proxy using a secret token that
+the Hub and Proxy agree upon. The value of this string should be a random
+string (for example, generated by ``openssl rand -hex 32``).
+
+Generating and storing token in the configuration file
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Or you can set the value in the configuration file, ``jupyterhub_config.py``:
+
+.. code-block:: python
+
+ c.JupyterHub.proxy_auth_token = '0bc02bede919e99a26de1e2a7a5aadfaf6228de836ec39a05a6c6942831d8fe5'
+
+Generating and storing as an environment variable
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can pass this value of the proxy authentication token to the Hub and Proxy
+using the ``CONFIGPROXY_AUTH_TOKEN`` environment variable:
+
+.. code-block:: bash
+
+ export CONFIGPROXY_AUTH_TOKEN=$(openssl rand -hex 32)
+
+This environment variable needs to be visible to the Hub and Proxy.
+
+Default if token is not set
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you don't set the Proxy authentication token, the Hub will generate a random
+key itself, which means that any time you restart the Hub you **must also
+restart the Proxy**. If the proxy is a subprocess of the Hub, this should happen
+automatically (this is the default configuration).
+
.. _cookie-secret:
Cookie secret
@@ -145,42 +183,3 @@ itself, ``jupyterhub_config.py``, as a binary string:
If the cookie secret value changes for the Hub, all single-user notebook
servers must also be restarted.
-
-
-.. _authentication-token:
-
-Proxy authentication token
---------------------------
-
-The Hub authenticates its requests to the Proxy using a secret token that
-the Hub and Proxy agree upon. The value of this string should be a random
-string (for example, generated by ``openssl rand -hex 32``).
-
-Generating and storing token in the configuration file
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Or you can set the value in the configuration file, ``jupyterhub_config.py``:
-
-.. code-block:: python
-
- c.JupyterHub.proxy_auth_token = '0bc02bede919e99a26de1e2a7a5aadfaf6228de836ec39a05a6c6942831d8fe5'
-
-Generating and storing as an environment variable
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can pass this value of the proxy authentication token to the Hub and Proxy
-using the ``CONFIGPROXY_AUTH_TOKEN`` environment variable:
-
-.. code-block:: bash
-
- export CONFIGPROXY_AUTH_TOKEN=$(openssl rand -hex 32)
-
-This environment variable needs to be visible to the Hub and Proxy.
-
-Default if token is not set
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you don't set the Proxy authentication token, the Hub will generate a random
-key itself, which means that any time you restart the Hub you **must also
-restart the Proxy**. If the proxy is a subprocess of the Hub, this should happen
-automatically (this is the default configuration).
From b92efcd7b064315e63219a4ef807c40cf541484e Mon Sep 17 00:00:00 2001
From: Min RK
Date: Thu, 20 Feb 2020 09:37:08 +0100
Subject: [PATCH 07/20] spawner test assumed app.users is populated
---
jupyterhub/tests/test_spawner.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/jupyterhub/tests/test_spawner.py b/jupyterhub/tests/test_spawner.py
index 177c1da0..fa0cbeab 100644
--- a/jupyterhub/tests/test_spawner.py
+++ b/jupyterhub/tests/test_spawner.py
@@ -103,7 +103,8 @@ async def wait_for_spawner(spawner, timeout=10):
async def test_single_user_spawner(app, request):
- user = next(iter(app.users.values()), None)
+ orm_user = app.db.query(orm.User).first()
+ user = app.users[orm_user]
spawner = user.spawner
spawner.cmd = ['jupyterhub-singleuser']
await user.spawn()
From a0b6d8ec6f60877d3c90d71e564c5ef3de6e9e89 Mon Sep 17 00:00:00 2001
From: Min RK
Date: Thu, 20 Feb 2020 12:12:55 +0100
Subject: [PATCH 08/20] add allow_implicit_spawn setting
- warn that there are known issues associated with enabling it
- it is inherently incompatible with named servers
---
jupyterhub/app.py | 28 ++++++++++++++++++++++++++++
jupyterhub/handlers/base.py | 8 +++++++-
2 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
index 0bd57de7..9df85c66 100644
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -917,10 +917,37 @@ class JupyterHub(Application):
def _authenticator_default(self):
return self.authenticator_class(parent=self, db=self.db)
+ allow_implicit_spawn = Bool(
+ False,
+ help="""Allow implicit spawning
+
+ When a user visits a URL for a server that's not running,
+ instead of confirming the spawn request,
+ automatically begin the spawn process.
+
+ Warning: not compatible with named servers,
+ and known to cause issues with redirect loops,
+ server errors, and infinitely respawning servers.
+ """,
+ ).tag(config=True)
+
+ @validate('allow_implicit_spawn')
+ def _allow_named_changed(self, proposal):
+ if proposal.value and self.allow_named_servers:
+ self.log.warning("Implicit spawn cannot work with named servers")
+ return False
+ return proposal.value
+
allow_named_servers = Bool(
False, help="Allow named single-user servers per user"
).tag(config=True)
+ @observe('allow_named_servers')
+ def _allow_named_changed(self, change):
+ if change.new and self.allow_implicit_spawn:
+ self.log.warning("Implicit spawn cannot work with named servers")
+ self.allow_implicit_spawn = False
+
named_server_limit_per_user = Integer(
0,
help="""
@@ -2158,6 +2185,7 @@ class JupyterHub(Application):
subdomain_host=self.subdomain_host,
domain=self.domain,
statsd=self.statsd,
+ allow_implicit_spawn=self.allow_implicit_spawn,
allow_named_servers=self.allow_named_servers,
default_server_name=self._default_server_name,
named_server_limit_per_user=self.named_server_limit_per_user,
diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py
index 77c2ae27..9910cdde 100644
--- a/jupyterhub/handlers/base.py
+++ b/jupyterhub/handlers/base.py
@@ -1426,11 +1426,17 @@ class UserUrlHandler(BaseHandler):
# serve a page prompting for spawn and 503 error
# visiting /user/:name no longer triggers implicit spawn
# without explicit user action
- self.set_status(503)
spawn_url = url_concat(
url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name),
{"next": self.request.uri},
)
+ if self.settings["allow_implicit_spawn"]:
+ self.log.warning("Allowing implicit spawn for %s", self.request.path)
+ self.redirect(spawn_url)
+ return
+ else:
+ self.set_status(503)
+
auth_state = await user.get_auth_state()
html = self.render_template(
"not_running.html",
From 436757dd5547319b409e13756c5103a331c8abcb Mon Sep 17 00:00:00 2001
From: Min RK
Date: Thu, 20 Feb 2020 12:43:39 +0100
Subject: [PATCH 09/20] handle implicit spawn with a javascript redirect
less dangerous than using a Location redirect, so remove conflicts
delay is a user-configurable timer (0 = no implicit spawn, default)
---
jupyterhub/app.py | 36 ++++++++-------------
jupyterhub/handlers/base.py | 8 ++---
share/jupyterhub/templates/not_running.html | 24 +++++++++++++-
3 files changed, 39 insertions(+), 29 deletions(-)
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
index 9df85c66..ee52bd9a 100644
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -917,37 +917,29 @@ class JupyterHub(Application):
def _authenticator_default(self):
return self.authenticator_class(parent=self, db=self.db)
- allow_implicit_spawn = Bool(
- False,
- help="""Allow implicit spawning
+ implicit_spawn_seconds = Float(
+ 0,
+ help="""Trigger implicit spawns after this many seconds.
When a user visits a URL for a server that's not running,
- instead of confirming the spawn request,
- automatically begin the spawn process.
+ they are shown a page indicating that the requested server
+ is not running with a button to spawn the server.
- Warning: not compatible with named servers,
- and known to cause issues with redirect loops,
- server errors, and infinitely respawning servers.
+ Setting this to a positive value will redirect the user
+ after this many seconds, effectively clicking this button
+ automatically for the users,
+ automatically beginning the spawn process.
+
+ Warning: this can result in errors and surprising behavior
+ when sharing access URLs to actual servers,
+ since the wrong server is likely to be started.
""",
).tag(config=True)
- @validate('allow_implicit_spawn')
- def _allow_named_changed(self, proposal):
- if proposal.value and self.allow_named_servers:
- self.log.warning("Implicit spawn cannot work with named servers")
- return False
- return proposal.value
-
allow_named_servers = Bool(
False, help="Allow named single-user servers per user"
).tag(config=True)
- @observe('allow_named_servers')
- def _allow_named_changed(self, change):
- if change.new and self.allow_implicit_spawn:
- self.log.warning("Implicit spawn cannot work with named servers")
- self.allow_implicit_spawn = False
-
named_server_limit_per_user = Integer(
0,
help="""
@@ -2185,7 +2177,7 @@ class JupyterHub(Application):
subdomain_host=self.subdomain_host,
domain=self.domain,
statsd=self.statsd,
- allow_implicit_spawn=self.allow_implicit_spawn,
+ implicit_spawn_seconds=self.implicit_spawn_seconds,
allow_named_servers=self.allow_named_servers,
default_server_name=self._default_server_name,
named_server_limit_per_user=self.named_server_limit_per_user,
diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py
index 9910cdde..9606fa61 100644
--- a/jupyterhub/handlers/base.py
+++ b/jupyterhub/handlers/base.py
@@ -1430,12 +1430,7 @@ class UserUrlHandler(BaseHandler):
url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name),
{"next": self.request.uri},
)
- if self.settings["allow_implicit_spawn"]:
- self.log.warning("Allowing implicit spawn for %s", self.request.path)
- self.redirect(spawn_url)
- return
- else:
- self.set_status(503)
+ self.set_status(503)
auth_state = await user.get_auth_state()
html = self.render_template(
@@ -1444,6 +1439,7 @@ class UserUrlHandler(BaseHandler):
server_name=server_name,
spawn_url=spawn_url,
auth_state=auth_state,
+ implicit_spawn_seconds=self.settings.get("implicit_spawn_seconds", 0),
)
self.finish(html)
diff --git a/share/jupyterhub/templates/not_running.html b/share/jupyterhub/templates/not_running.html
index 182e7ba0..fa493d97 100644
--- a/share/jupyterhub/templates/not_running.html
+++ b/share/jupyterhub/templates/not_running.html
@@ -23,7 +23,14 @@
{% endif %}
Would you like to retry starting it?
{% else %}
- Your server {{ server_name }} is not running. Would you like to start it?
+ Your server {{ server_name }} is not running.
+ {% if implicit_spawn_seconds %}
+ It will be restarted automatically.
+ If you are not redirected in a few seconds,
+ click below to launch your server.
+ {% else %}
+ Would you like to start it?
+ {% endif %}
{% endif %}
{% endblock %}
@@ -42,3 +49,18 @@
{% endblock %}
+{% block script %}
+{{ super () }}
+{% if implicit_spawn_seconds %}
+
+{% endif %}
+{% endblock script %}
From 0787489e1bbac11d14482a96c1179755a210d2ba Mon Sep 17 00:00:00 2001
From: Min RK
Date: Thu, 20 Feb 2020 12:53:15 +0100
Subject: [PATCH 10/20] maybe_future needs a future!
---
jupyterhub/app.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
index b0cc694a..93f56de7 100644
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -1675,7 +1675,7 @@ class JupyterHub(Application):
try:
f = self.authenticator.add_user(user)
if f:
- await maybe_future()
+ await maybe_future(f)
except Exception:
self.log.exception("Error adding user %s already in db", user.name)
if self.authenticator.delete_invalid_users:
From d2a1b8e349786de66dcf26011f4cfa138113d07b Mon Sep 17 00:00:00 2001
From: Min RK
Date: Thu, 20 Feb 2020 15:31:38 +0100
Subject: [PATCH 11/20] update docs environments
- python 3.7
- node 12
- sync recommonmark 0.6
---
docs/environment.yml | 6 +++---
docs/requirements.txt | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/environment.yml b/docs/environment.yml
index 35b5c405..4ba57441 100644
--- a/docs/environment.yml
+++ b/docs/environment.yml
@@ -5,12 +5,12 @@ channels:
- conda-forge
dependencies:
- pip
-- nodejs
-- python=3.6
+- nodejs=12
+- python=3.7
- alembic
- jinja2
- pamela
-- recommonmark==0.6.0
+- recommonmark>=0.6
- requests
- sqlalchemy>=1
- tornado>=5.0
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 01d85db1..4934ec4d 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -4,7 +4,7 @@
alabaster_jupyterhub
autodoc-traits
git+https://github.com/pandas-dev/pandas-sphinx-theme.git@master
-recommonmark==0.5.0
+recommonmark>=0.6
sphinx-copybutton
sphinx-jsonschema
sphinx>=1.7
From 0c4db2d99f5a2ba513cbc6c9efc175d5420272b5 Mon Sep 17 00:00:00 2001
From: Min RK
Date: Thu, 20 Feb 2020 15:54:43 +0100
Subject: [PATCH 12/20] docs: use metachannel for faster environment solve
rtd is having memory issues with conda-forge, which should hopefully be fixed by metachannel
this should also make things quicker for anyone
---
docs/environment.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/docs/environment.yml b/docs/environment.yml
index 4ba57441..0b613bad 100644
--- a/docs/environment.yml
+++ b/docs/environment.yml
@@ -2,7 +2,10 @@
# if you change the dependencies of JupyterHub in the various `requirements.txt`
name: jhub_docs
channels:
- - conda-forge
+ # use metachannel for faster solves / less memory on RTD
+ # which has memory issues with full conda-forge
+ # only need to list 'leaf' dependencies here
+ - https://metachannel.conda-forge.org/conda-forge/jupyterhub,sphinx,recommonmark
dependencies:
- pip
- nodejs=12
From 606775f72da2ce648a5a5fbe8e452c72f082e263 Mon Sep 17 00:00:00 2001
From: Tim Head
Date: Thu, 20 Feb 2020 16:56:03 +0100
Subject: [PATCH 13/20] Remove unused variable
---
jupyterhub/app.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
index 93f56de7..41abcf4c 100644
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -2019,7 +2019,6 @@ class JupyterHub(Application):
if not orm_spawners:
continue
orm_spawner = orm_spawners[0]
- orm_user = orm_spawner.user
# instantiate Spawner wrapper and check if it's still alive
# spawner should be running
user = self.users[orm_spawner.user]
From 287b0302d9e6677a826727c973f39e66ea5bc6d5 Mon Sep 17 00:00:00 2001
From: "Bruno P. Kinoshita"
Date: Wed, 19 Feb 2020 13:46:45 +1300
Subject: [PATCH 14/20] Add more docs about authentication and cookies, using
text posted by MinRK on Discourse
---
.../getting-started/security-basics.rst | 71 ++++++++++++++++++-
1 file changed, 69 insertions(+), 2 deletions(-)
diff --git a/docs/source/getting-started/security-basics.rst b/docs/source/getting-started/security-basics.rst
index 7661cfd1..6704e7ce 100644
--- a/docs/source/getting-started/security-basics.rst
+++ b/docs/source/getting-started/security-basics.rst
@@ -86,7 +86,7 @@ Proxy authentication token
--------------------------
The Hub authenticates its requests to the Proxy using a secret token that
-the Hub and Proxy agree upon. The value of this string should be a random
+the Hub and Proxy agree upon. The value of this token should be a random
string (for example, generated by ``openssl rand -hex 32``).
Generating and storing token in the configuration file
@@ -96,7 +96,7 @@ Or you can set the value in the configuration file, ``jupyterhub_config.py``:
.. code-block:: python
- c.JupyterHub.proxy_auth_token = '0bc02bede919e99a26de1e2a7a5aadfaf6228de836ec39a05a6c6942831d8fe5'
+ c.ConfigurableHTTPProxy.api_token = 'abc123...' # any random string
Generating and storing as an environment variable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -183,3 +183,70 @@ itself, ``jupyterhub_config.py``, as a binary string:
If the cookie secret value changes for the Hub, all single-user notebook
servers must also be restarted.
+
+.. _cookies:
+
+Cookies used by JupyterHub authentication
+-----------------------------------------
+
+The following cookies are used by the Hub for handling user authentication.
+
+jupyterhub-hub-login
+~~~~~~~~~~~~~~~~~~~~
+
+This is the login token used when visiting Hub-served pages encrypted such
+as the main home, the spawn form, etc. If this cookie is set, then the
+user is logged in.
+
+Resetting the Hub cookie secret effectively revokes this cookie.
+
+This cookie is restricted to the path ``/hub/``.
+
+jupyterhub-user-
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the cookie used for authenticating with a single-user server
+encrypted. It is set by the single-user server after OAuth with the Hub.
+
+Effectively the same as ``jupyterhub-hub-login``, but for the
+single-user server instead of the Hub. It contains an OAuth access token,
+which is checked with the Hub to authenticate the browser.
+
+Each OAuth access token is associated with a session id (see ``jupyterhub-session-id`` section
+below).
+
+To avoid hitting the Hub on every request, the authentication response
+is cached. And to avoid a stale cache the cache key is comprised of both
+the token and session id.
+
+Resetting the Hub cookie secret effectively revokes this cookie.
+
+This cookie is restricted to the path ``/user/``, so that
+only the user’s server receives it.
+
+jupyterhub-session-id
+~~~~~~~~~~~~~~~~~~~~~
+
+This is a random string, meaningless in itself, and the only cookie
+shared by the Hub and single-user servers.
+
+Its sole purpose is to to coordinate logout of the multiple OAuth cookies.
+
+This cookie is set to ``/`` so all endpoints can receive it, or clear it, etc.
+
+jupyterhub-user--oauth-state
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A short-lived cookie, used solely to store and validate OAuth state.
+It is only set while OAuth between the single-user server and the Hub
+is processing.
+
+If you use your browser development tools, you should see this cookie
+for a very brief moment before your are logged in,
+with an expiration date shorter than ``jupyterhub-hub-login`` or
+``jupyterhub-user-``.
+
+This cookie should not exist after you have successfully logged in.
+
+This cookie is restricted to the path ``/user/``, so that only
+the user’s server receives it.
From f5bd5b7751f87dd0ac229be8db909f199cd39a09 Mon Sep 17 00:00:00 2001
From: "Bruno P. Kinoshita"
Date: Fri, 21 Feb 2020 10:32:11 +1300
Subject: [PATCH 15/20] Incorporate review feedback
---
.../getting-started/security-basics.rst | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/docs/source/getting-started/security-basics.rst b/docs/source/getting-started/security-basics.rst
index 6704e7ce..1a79f2e6 100644
--- a/docs/source/getting-started/security-basics.rst
+++ b/docs/source/getting-started/security-basics.rst
@@ -87,12 +87,13 @@ Proxy authentication token
The Hub authenticates its requests to the Proxy using a secret token that
the Hub and Proxy agree upon. The value of this token should be a random
-string (for example, generated by ``openssl rand -hex 32``).
+string (for example, generated by ``openssl rand -hex 32``). You can store
+it in the configuration file or an environment variable
Generating and storing token in the configuration file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Or you can set the value in the configuration file, ``jupyterhub_config.py``:
+You can set the value in the configuration file, ``jupyterhub_config.py``:
.. code-block:: python
@@ -191,12 +192,16 @@ Cookies used by JupyterHub authentication
The following cookies are used by the Hub for handling user authentication.
+This section was created based on this post_ from Discourse.
+
+.. _post: https://discourse.jupyter.org/t/how-to-force-re-login-for-users/1998/6
+
jupyterhub-hub-login
~~~~~~~~~~~~~~~~~~~~
-This is the login token used when visiting Hub-served pages encrypted such
-as the main home, the spawn form, etc. If this cookie is set, then the
-user is logged in.
+This is the login token used when visiting Hub-served pages that are
+protected by authentication such as the main home, the spawn form, etc.
+If this cookie is set, then the user is logged in.
Resetting the Hub cookie secret effectively revokes this cookie.
@@ -205,8 +210,8 @@ This cookie is restricted to the path ``/hub/``.
jupyterhub-user-
~~~~~~~~~~~~~~~~~~~~~~~~~~
-This is the cookie used for authenticating with a single-user server
-encrypted. It is set by the single-user server after OAuth with the Hub.
+This is the cookie used for authenticating with a single-user server.
+It is set by the single-user server after OAuth with the Hub.
Effectively the same as ``jupyterhub-hub-login``, but for the
single-user server instead of the Hub. It contains an OAuth access token,
From 239a4c63a2e644f44297c0f330f8985cd25716b0 Mon Sep 17 00:00:00 2001
From: "Bruno P. Kinoshita"
Date: Fri, 21 Feb 2020 10:35:30 +1300
Subject: [PATCH 16/20] Add note that not all proxy implementations use an auth
token
---
docs/source/getting-started/security-basics.rst | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/docs/source/getting-started/security-basics.rst b/docs/source/getting-started/security-basics.rst
index 1a79f2e6..5da0a668 100644
--- a/docs/source/getting-started/security-basics.rst
+++ b/docs/source/getting-started/security-basics.rst
@@ -86,9 +86,13 @@ Proxy authentication token
--------------------------
The Hub authenticates its requests to the Proxy using a secret token that
-the Hub and Proxy agree upon. The value of this token should be a random
-string (for example, generated by ``openssl rand -hex 32``). You can store
-it in the configuration file or an environment variable
+the Hub and Proxy agree upon. Note that this applies to the default
+``ConfigurableHTTPProxy`` implementation. Not all proxy implementations
+use an auth token.
+
+The value of this token should be a random string (for example, generated by
+``openssl rand -hex 32``). You can store it in the configuration file or an
+environment variable
Generating and storing token in the configuration file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
From 3e6abb7a5e399b1aebfd85ce388c1d9d1962bfba Mon Sep 17 00:00:00 2001
From: Min RK
Date: Fri, 21 Feb 2020 13:52:03 +0100
Subject: [PATCH 17/20] add general faq
and put a first q about user-redirect
---
docs/source/getting-started/faq.md | 36 +++++++++++++++++++++++++++
docs/source/getting-started/index.rst | 1 +
2 files changed, 37 insertions(+)
create mode 100644 docs/source/getting-started/faq.md
diff --git a/docs/source/getting-started/faq.md b/docs/source/getting-started/faq.md
new file mode 100644
index 00000000..ae912847
--- /dev/null
+++ b/docs/source/getting-started/faq.md
@@ -0,0 +1,36 @@
+# Frequently asked questions
+
+
+### How do I share links to notebooks?
+
+In short, where you see `/user/name/notebooks/foo.ipynb` use `/hub/user-redirect/notebooks/foo.ipynb` (replace `/user/name` with `/hub/user-redirect`).
+
+Sharing links to notebooks is a common activity,
+and can look different based on what you mean.
+Your first instinct might be to copy the URL you see in the browser,
+e.g. `hub.jupyter.org/user/yourname/notebooks/coolthing.ipynb`.
+However, let's break down what this URL means:
+
+`hub.jupyter.org/user/yourname/` is the URL prefix handled by *your server*,
+which means that sharing this URL is asking the person you share the link with
+to come to *your server* and look at the exact same file.
+In most circumstances, this is forbidden by permissions because the person you share with does not have access to your server.
+What actually happens when someone visits this URL will depend on whether your server is running and other factors.
+
+But what is our actual goal?
+A typical situation is that you have some shared or common filesystem,
+such that the same path corresponds to the same document
+(either the exact same document or another copy of it).
+Typically, what folks want when they do sharing like this
+is for each visitor to open the same file *on their own server*,
+so Breq would open `/user/breq/notebooks/foo.ipynb` and
+Seivarden would open `/user/seivarden/notebooks/foo.ipynb`, etc.
+
+JupyterHub has a special URL that does exactly this!
+It's called `/hub/user-redirect/...` and after the visitor logs in,
+So if you replace `/user/yourname` in your URL bar
+with `/hub/user-redirect` any visitor should get the same
+URL on their own server, rather than visiting yours.
+
+In JupyterLab 2.0, this should also be the result of the "Copy Shareable Link"
+action in the file browser.
diff --git a/docs/source/getting-started/index.rst b/docs/source/getting-started/index.rst
index 00bcdfbc..bae95f8f 100644
--- a/docs/source/getting-started/index.rst
+++ b/docs/source/getting-started/index.rst
@@ -15,4 +15,5 @@ own JupyterHub.
authenticators-users-basics
spawners-basics
services-basics
+ faq
institutional-faq
From 7e3caf7f4870567775b1b34f95afc5112ecbc541 Mon Sep 17 00:00:00 2001
From: Alex Driedger
Date: Sat, 22 Feb 2020 16:37:34 -0800
Subject: [PATCH 18/20] Fixed grammar on landing page
---
docs/source/index.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 1f819278..c9722089 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -3,7 +3,7 @@ JupyterHub
==========
`JupyterHub`_ is the best way to serve `Jupyter notebook`_ for multiple users.
-It can be used in a classes of students, a corporate data science group or scientific
+It can be used in a class of students, a corporate data science group or scientific
research group. It is a multi-user **Hub** that spawns, manages, and proxies multiple
instances of the single-user `Jupyter notebook`_ server.
From 76afec8adb8e30b6102854ebf79bbe1c0726c27c Mon Sep 17 00:00:00 2001
From: "Bruno P. Kinoshita"
Date: Sat, 12 Oct 2019 12:44:02 +1300
Subject: [PATCH 19/20] Update app.bind_url and proxy.public_url when
(external) SSL is enabled
---
jupyterhub/app.py | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/jupyterhub/app.py b/jupyterhub/app.py
index 0cf649bf..dda4c127 100644
--- a/jupyterhub/app.py
+++ b/jupyterhub/app.py
@@ -576,6 +576,22 @@ class JupyterHub(Application):
""",
).tag(config=True)
+ @validate('bind_url')
+ def _validate_bind_url(self, proposal):
+ """ensure protocol field of bind_url matches ssl"""
+ v = proposal['value']
+ proto, sep, rest = v.partition('://')
+ if self.ssl_cert and proto != 'https':
+ return 'https' + sep + rest
+ elif proto != 'http' and not self.ssl_cert:
+ return 'http' + sep + rest
+ return v
+
+ @default('bind_url')
+ def _bind_url_default(self):
+ proto = 'https' if self.ssl_cert else 'http'
+ return proto + '://:8000'
+
subdomain_host = Unicode(
'',
help="""Run single-user servers on subdomains of this host.
From 3b05135f11dc3926ff30adf78eae21e767b7a4f3 Mon Sep 17 00:00:00 2001
From: "Bruno P. Kinoshita"
Date: Mon, 24 Feb 2020 20:48:42 +1300
Subject: [PATCH 20/20] Fix couple typos
---
docs/source/contributing/tests.rst | 2 +-
docs/source/getting-started/security-basics.rst | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/source/contributing/tests.rst b/docs/source/contributing/tests.rst
index 46360eb7..c607df92 100644
--- a/docs/source/contributing/tests.rst
+++ b/docs/source/contributing/tests.rst
@@ -64,5 +64,5 @@ Troubleshooting Test Failures
All the tests are failing
-------------------------
-Make sure you have completed all the steps in :ref:`contributing/setup` sucessfully, and
+Make sure you have completed all the steps in :ref:`contributing/setup` successfully, and
can launch ``jupyterhub`` from the terminal.
diff --git a/docs/source/getting-started/security-basics.rst b/docs/source/getting-started/security-basics.rst
index 5da0a668..87007311 100644
--- a/docs/source/getting-started/security-basics.rst
+++ b/docs/source/getting-started/security-basics.rst
@@ -239,7 +239,7 @@ jupyterhub-session-id
This is a random string, meaningless in itself, and the only cookie
shared by the Hub and single-user servers.
-Its sole purpose is to to coordinate logout of the multiple OAuth cookies.
+Its sole purpose is to coordinate logout of the multiple OAuth cookies.
This cookie is set to ``/`` so all endpoints can receive it, or clear it, etc.