mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 01:54:09 +00:00
call client-allowed scopes JUPYTERHUB_OAUTH_CLIENT_ALLOWED_SCOPES
This commit is contained in:
@@ -235,7 +235,7 @@ against the `.hub_scopes` attribute of each Handler
|
||||
|
||||
:::{versionchanged} 2.4
|
||||
The JUPYTERHUB_OAUTH_SCOPES environment variable is deprecated and renamed to JUPYTERHUB_OAUTH_ACCESS_SCOPES,
|
||||
to avoid ambiguity with JUPYTERHUB_OAUTH_ALLOWED_SCOPES
|
||||
to avoid ambiguity with JUPYTERHUB_OAUTH_CLIENT_ALLOWED_SCOPES
|
||||
:::
|
||||
|
||||
```python
|
||||
|
@@ -119,7 +119,7 @@ JUPYTERHUB_SERVICE_URL: Local URL where the service is expected to be listeni
|
||||
JUPYTERHUB_OAUTH_SCOPES: JSON-serialized list of scopes to use for allowing access to the service
|
||||
(deprecated in 2.4, use JUPYTERHUB_OAUTH_ACCESS_SCOPES).
|
||||
JUPYTERHUB_OAUTH_ACCESS_SCOPES: JSON-serialized list of scopes to use for allowing access to the service (new in 2.4).
|
||||
JUPYTERHUB_OAUTH_ALLOWED_SCOPES: JSON-serialized list of scopes that can be requested on behalf of users (new in 2.4).
|
||||
JUPYTERHUB_OAUTH_CLIENT_ALLOWED_SCOPES: JSON-serialized list of scopes that can be requested by the oauth client on behalf of users (new in 2.4).
|
||||
```
|
||||
|
||||
For the previous 'cull idle' Service example, these environment variables
|
||||
|
@@ -309,7 +309,7 @@ The process environment is returned by `Spawner.get_env`, which specifies the fo
|
||||
- JUPYTERHUB_CLIENT_ID - the OAuth client ID for authenticating visitors.
|
||||
- JUPYTERHUB_OAUTH_CALLBACK_URL - the callback URL to use in oauth, typically `/user/:name/oauth_callback`
|
||||
- JUPYTERHUB_OAUTH_ACCESS_SCOPES - the scopes required to access the server (called JUPYTERHUB_OAUTH_SCOPES prior to 2.4)
|
||||
- JUPYTERHUB_OAUTH_ALLOWED_SCOPES - the scopes the service is allowed to request.
|
||||
- JUPYTERHUB_OAUTH_CLIENT_ALLOWED_SCOPES - the scopes the service is allowed to request.
|
||||
If no scopes are requested explicitly, these scopes will be requested.
|
||||
|
||||
Optional environment variables, depending on configuration:
|
||||
|
@@ -7,7 +7,7 @@ c.JupyterHub.services = [
|
||||
'name': 'grades',
|
||||
'url': 'http://127.0.0.1:10101',
|
||||
'command': [sys.executable, './grades.py'],
|
||||
'oauth_allowed_scopes': [
|
||||
'oauth_client_allowed_scopes': [
|
||||
'custom:grades:write',
|
||||
'custom:grades:read',
|
||||
],
|
||||
|
@@ -26,7 +26,7 @@ After logging in with any username and password, you should see a JSON dump of y
|
||||
```
|
||||
|
||||
What is contained in the model will depend on the permissions
|
||||
requested in the `oauth_allowed_scopes` configuration of the service `whoami-oauth` service.
|
||||
requested in the `oauth_client_allowed_scopes` configuration of the service `whoami-oauth` service.
|
||||
The default is the minimum required for identification and access to the service,
|
||||
which will provide the username and current scopes.
|
||||
|
||||
|
@@ -14,11 +14,11 @@ c.JupyterHub.services = [
|
||||
# only requesting access to the service,
|
||||
# and identification by name,
|
||||
# nothing more.
|
||||
# Specifying 'oauth_allowed_scopes' as a list of scopes
|
||||
# Specifying 'oauth_client_allowed_scopes' as a list of scopes
|
||||
# allows requesting more information about users,
|
||||
# or the ability to take actions on users' behalf, as required.
|
||||
# the 'inherit' scope means the full permissions of the owner
|
||||
# 'oauth_allowed_scopes': ['inherit'],
|
||||
# 'oauth_client_allowed_scopes': ['inherit'],
|
||||
},
|
||||
]
|
||||
|
||||
|
@@ -2373,8 +2373,8 @@ class JupyterHub(Application):
|
||||
|
||||
if service.oauth_available:
|
||||
allowed_scopes = set()
|
||||
if service.oauth_allowed_scopes:
|
||||
allowed_scopes.update(service.oauth_allowed_scopes)
|
||||
if service.oauth_client_allowed_scopes:
|
||||
allowed_scopes.update(service.oauth_client_allowed_scopes)
|
||||
if service.oauth_roles:
|
||||
if not allowed_scopes:
|
||||
# DEPRECATED? It's still convenient and valid,
|
||||
@@ -2388,7 +2388,7 @@ class JupyterHub(Application):
|
||||
else:
|
||||
self.log.warning(
|
||||
f"Ignoring oauth_roles for {service.name}: {service.oauth_roles},"
|
||||
f" using oauth_allowed_scopes={allowed_scopes}."
|
||||
f" using oauth_client_allowed_scopes={allowed_scopes}."
|
||||
)
|
||||
oauth_client = self.oauth_provider.add_client(
|
||||
client_id=service.oauth_client_id,
|
||||
|
@@ -333,21 +333,30 @@ class HubAuth(SingletonConfigurable):
|
||||
def _default_cache(self):
|
||||
return _ExpiringDict(self.cache_max_age)
|
||||
|
||||
oauth_scopes = Set(
|
||||
@property
|
||||
def oauth_scopes(self):
|
||||
warnings.warn(
|
||||
"HubAuth.oauth_scopes is deprecated in JupyterHub 2.4. Use .access_scopes"
|
||||
)
|
||||
return self.access_scopes
|
||||
|
||||
access_scopes = Set(
|
||||
Unicode(),
|
||||
help="""OAuth scopes to use for allowing access.
|
||||
|
||||
Get from $JUPYTERHUB_OAUTH_SCOPES by default.
|
||||
Get from $JUPYTERHUB_OAUTH_ACCESS_SCOPES by default.
|
||||
""",
|
||||
).tag(config=True)
|
||||
|
||||
@default('oauth_scopes')
|
||||
@default('access_scopes')
|
||||
def _default_scopes(self):
|
||||
env_scopes = os.getenv('JUPYTERHUB_OAUTH_ACCESS_SCOPES')
|
||||
if not env_scopes:
|
||||
env_scopes = os.getenv('JUPYTERHUB_OAUTH_ACCESS_SCOPES')
|
||||
# deprecated name (since 2.4)
|
||||
env_scopes = os.getenv('JUPYTERHUB_OAUTH_SCOPES')
|
||||
if env_scopes:
|
||||
return set(json.loads(env_scopes))
|
||||
# scopes not specified, use service name if defined
|
||||
service_name = os.getenv("JUPYTERHUB_SERVICE_NAME")
|
||||
if service_name:
|
||||
return {f'access:services!service={service_name}'}
|
||||
@@ -865,7 +874,7 @@ class HubAuthenticated:
|
||||
|
||||
- .hub_auth: A HubAuth instance
|
||||
- .hub_scopes: A set of JupyterHub 2.0 OAuth scopes to allow.
|
||||
Default comes from .hub_auth.oauth_scopes,
|
||||
Default comes from .hub_auth.oauth_access_scopes,
|
||||
which in turn is set by $JUPYTERHUB_OAUTH_ACCESS_SCOPES
|
||||
Default values include:
|
||||
- 'access:services', 'access:services!service={service_name}' for services
|
||||
@@ -905,8 +914,8 @@ class HubAuthenticated:
|
||||
|
||||
@property
|
||||
def hub_scopes(self):
|
||||
"""Set of allowed scopes (use hub_auth.oauth_scopes by default)"""
|
||||
return self.hub_auth.oauth_scopes or None
|
||||
"""Set of allowed scopes (use hub_auth.access_scopes by default)"""
|
||||
return self.hub_auth.access_scopes or None
|
||||
|
||||
@property
|
||||
def allow_all(self):
|
||||
|
@@ -203,11 +203,11 @@ class Service(LoggingConfigurable):
|
||||
oauth_roles = List(
|
||||
help="""OAuth allowed roles.
|
||||
|
||||
DEPRECATED in 2.4: use oauth_allowed_scopes
|
||||
DEPRECATED in 2.4: use oauth_client_allowed_scopes
|
||||
"""
|
||||
).tag(input=True)
|
||||
|
||||
oauth_allowed_scopes = List(
|
||||
oauth_client_allowed_scopes = List(
|
||||
help="""OAuth allowed scopes.
|
||||
|
||||
This sets the maximum and default scopes
|
||||
|
@@ -306,7 +306,7 @@ class Spawner(LoggingConfigurable):
|
||||
[Callable(), List()],
|
||||
help="""Allowed roles for oauth tokens.
|
||||
|
||||
Deprecated in 2.4: use oauth_allowed_scopes
|
||||
Deprecated in 2.4: use oauth_client_allowed_scopes
|
||||
|
||||
This sets the maximum and default roles
|
||||
assigned to oauth tokens issued by a single-user server's
|
||||
@@ -318,9 +318,9 @@ class Spawner(LoggingConfigurable):
|
||||
""",
|
||||
).tag(config=True)
|
||||
|
||||
oauth_allowed_scopes = Union(
|
||||
oauth_client_allowed_scopes = Union(
|
||||
[Callable(), List()],
|
||||
help="""Allowed scopes for oauth tokens.
|
||||
help="""Allowed scopes for oauth tokens issued by this server's oauth client.
|
||||
|
||||
This sets the maximum and default scopes
|
||||
assigned to oauth tokens issued by a single-user server's
|
||||
@@ -332,12 +332,12 @@ class Spawner(LoggingConfigurable):
|
||||
""",
|
||||
).tag(config=True)
|
||||
|
||||
def _get_oauth_allowed_scopes(self):
|
||||
def _get_oauth_client_allowed_scopes(self):
|
||||
"""Private method: get oauth allowed scopes
|
||||
|
||||
Handle:
|
||||
|
||||
- oauth_allowed_scopes
|
||||
- oauth_client_allowed_scopes
|
||||
- callable config
|
||||
- deprecated oauth_roles config
|
||||
- access_scopes
|
||||
@@ -347,8 +347,8 @@ class Spawner(LoggingConfigurable):
|
||||
# 2. only roles
|
||||
# 3. both! (conflict, favor scopes)
|
||||
scopes = []
|
||||
if self.oauth_allowed_scopes:
|
||||
allowed_scopes = self.oauth_allowed_scopes
|
||||
if self.oauth_client_allowed_scopes:
|
||||
allowed_scopes = self.oauth_client_allowed_scopes
|
||||
if callable(allowed_scopes):
|
||||
allowed_scopes = allowed_scopes(self)
|
||||
scopes.extend(allowed_scopes)
|
||||
@@ -357,7 +357,7 @@ class Spawner(LoggingConfigurable):
|
||||
if scopes:
|
||||
# both defined! Warn
|
||||
warnings.warn(
|
||||
f"Ignoring deprecated Spawner.oauth_roles={self.oauth_roles} in favor of Spawner.oauth_allowed_scopes.",
|
||||
f"Ignoring deprecated Spawner.oauth_roles={self.oauth_roles} in favor of Spawner.oauth_client_allowed_scopes.",
|
||||
)
|
||||
else:
|
||||
role_names = self.oauth_roles
|
||||
@@ -960,7 +960,9 @@ class Spawner(LoggingConfigurable):
|
||||
env['JUPYTERHUB_OAUTH_ACCESS_SCOPES'] = json.dumps(self.oauth_access_scopes)
|
||||
|
||||
# added in 2.4
|
||||
env['JUPYTERHUB_OAUTH_ALLOWED_SCOPES'] = json.dumps(self.oauth_allowed_scopes)
|
||||
env['JUPYTERHUB_OAUTH_CLIENT_ALLOWED_SCOPES'] = json.dumps(
|
||||
self.oauth_client_allowed_scopes
|
||||
)
|
||||
|
||||
# Info previously passed on args
|
||||
env['JUPYTERHUB_USER'] = self.user.name
|
||||
|
@@ -894,18 +894,18 @@ async def test_server_role_api_calls(
|
||||
assert r.status_code == response
|
||||
|
||||
|
||||
async def test_oauth_allowed_scopes(app):
|
||||
async def test_oauth_client_allowed_scopes(app):
|
||||
allowed_scopes = ['read:users', 'read:groups']
|
||||
service = {
|
||||
'name': 'oas1',
|
||||
'api_token': 'some-token',
|
||||
'oauth_allowed_scopes': allowed_scopes,
|
||||
'oauth_client_allowed_scopes': allowed_scopes,
|
||||
}
|
||||
app.services.append(service)
|
||||
app.init_services()
|
||||
app_service = app.services[0]
|
||||
assert app_service['name'] == 'oas1'
|
||||
assert set(app_service['oauth_allowed_scopes']) == set(allowed_scopes)
|
||||
assert set(app_service['oauth_client_allowed_scopes']) == set(allowed_scopes)
|
||||
|
||||
|
||||
async def test_user_group_roles(app, create_temp_role):
|
||||
|
@@ -439,7 +439,7 @@ async def test_hub_connect_url(db):
|
||||
async def test_spawner_oauth_scopes(app, user):
|
||||
allowed_scopes = ["read:users"]
|
||||
spawner = user.spawners['']
|
||||
spawner.oauth_allowed_scopes = allowed_scopes
|
||||
spawner.oauth_client_allowed_scopes = allowed_scopes
|
||||
# exercise start/stop which assign roles to oauth client
|
||||
await spawner.user.spawn()
|
||||
oauth_client = spawner.orm_spawner.oauth_client
|
||||
|
@@ -670,7 +670,7 @@ class User:
|
||||
client_id,
|
||||
api_token,
|
||||
url_path_join(self.url, url_escape_path(server_name), 'oauth_callback'),
|
||||
allowed_scopes=spawner._get_oauth_allowed_scopes(),
|
||||
allowed_scopes=spawner._get_oauth_client_allowed_scopes(),
|
||||
description="Server at %s"
|
||||
% (url_path_join(self.base_url, server_name) + '/'),
|
||||
)
|
||||
|
Reference in New Issue
Block a user