mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 02:24:08 +00:00
rename white/blacklist allowed/blocked
- group_whitelist -> allowed_groups still todo: handle deprecated signatures in check_whitelist methods while preserving subclass overrides
This commit is contained in:
@@ -201,7 +201,7 @@ class OAuthAuthorizeHandler(OAuthHandler, BaseHandler):
|
||||
def needs_oauth_confirm(self, user, oauth_client):
|
||||
"""Return whether the given oauth client needs to prompt for access for the given user
|
||||
|
||||
Checks whitelist for oauth clients
|
||||
Checks list for oauth clients that don't need confirmation
|
||||
|
||||
(i.e. the user's own server)
|
||||
|
||||
@@ -214,9 +214,8 @@ class OAuthAuthorizeHandler(OAuthHandler, BaseHandler):
|
||||
if (
|
||||
# it's the user's own server
|
||||
oauth_client.identifier in own_oauth_client_ids
|
||||
# or it's in the global whitelist
|
||||
or oauth_client.identifier
|
||||
in self.settings.get('oauth_no_confirm_whitelist', set())
|
||||
# or it's in the global no-confirm list
|
||||
or oauth_client.identifier in self.settings.get('oauth_no_confirm', set())
|
||||
):
|
||||
return False
|
||||
# default: require confirmation
|
||||
@@ -229,7 +228,7 @@ class OAuthAuthorizeHandler(OAuthHandler, BaseHandler):
|
||||
Render oauth confirmation page:
|
||||
"Server at ... would like permission to ...".
|
||||
|
||||
Users accessing their own server or a service whitelist
|
||||
Users accessing their own server or a blessed service
|
||||
will skip confirmation.
|
||||
"""
|
||||
|
||||
|
@@ -1689,22 +1689,22 @@ class JupyterHub(Application):
|
||||
# the admin_users config variable will never be used after this point.
|
||||
# only the database values will be referenced.
|
||||
|
||||
whitelist = [
|
||||
allowed = [
|
||||
self.authenticator.normalize_username(name)
|
||||
for name in self.authenticator.whitelist
|
||||
for name in self.authenticator.allowed
|
||||
]
|
||||
self.authenticator.whitelist = set(whitelist) # force normalization
|
||||
for username in whitelist:
|
||||
self.authenticator.allowed = set(allowed) # force normalization
|
||||
for username in allowed:
|
||||
if not self.authenticator.validate_username(username):
|
||||
raise ValueError("username %r is not valid" % username)
|
||||
|
||||
if not whitelist:
|
||||
if not allowed:
|
||||
self.log.info(
|
||||
"Not using whitelist. Any authenticated user will be allowed."
|
||||
"Not using allowed user list. Any authenticated user will be allowed."
|
||||
)
|
||||
|
||||
# add whitelisted users to the db
|
||||
for name in whitelist:
|
||||
# add allowed users to the db
|
||||
for name in allowed:
|
||||
user = orm.User.find(db, name)
|
||||
if user is None:
|
||||
user = orm.User(name=name)
|
||||
@@ -1714,9 +1714,9 @@ class JupyterHub(Application):
|
||||
db.commit()
|
||||
|
||||
# Notify authenticator of all users.
|
||||
# This ensures Auth whitelist is up-to-date with the database.
|
||||
# This lets whitelist be used to set up initial list,
|
||||
# but changes to the whitelist can occur in the database,
|
||||
# This ensures Authenticator.allowed is up-to-date with the database.
|
||||
# This lets .allowed be used to set up initial list,
|
||||
# but changes to the allowed list can occur in the database,
|
||||
# and persist across sessions.
|
||||
total_users = 0
|
||||
for user in db.query(orm.User):
|
||||
@@ -1753,9 +1753,9 @@ class JupyterHub(Application):
|
||||
user.created = user.last_activity or datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
# The whitelist set and the users in the db are now the same.
|
||||
# The allowed set and the users in the db are now the same.
|
||||
# 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).
|
||||
# to the allowed set and user db, unless the allowed set is empty (all users allowed).
|
||||
|
||||
TOTAL_USERS.set(total_users)
|
||||
|
||||
@@ -1770,11 +1770,11 @@ class JupyterHub(Application):
|
||||
for username in usernames:
|
||||
username = self.authenticator.normalize_username(username)
|
||||
if not (
|
||||
await maybe_future(
|
||||
self.authenticator.check_whitelist(username, None)
|
||||
)
|
||||
await maybe_future(self.authenticator.check_allowed(username, None))
|
||||
):
|
||||
raise ValueError("Username %r is not in whitelist" % username)
|
||||
raise ValueError(
|
||||
"Username %r is not in Authenticator.allowed" % username
|
||||
)
|
||||
user = orm.User.find(db, name=username)
|
||||
if user is None:
|
||||
if not self.authenticator.validate_username(username):
|
||||
@@ -1798,11 +1798,13 @@ class JupyterHub(Application):
|
||||
if kind == 'user':
|
||||
name = self.authenticator.normalize_username(name)
|
||||
if not (
|
||||
await maybe_future(self.authenticator.check_whitelist(name, None))
|
||||
await maybe_future(self.authenticator.check_allowed(name, None))
|
||||
):
|
||||
raise ValueError("Token name %r is not in whitelist" % name)
|
||||
raise ValueError(
|
||||
"Token user name %r is not in Authenticator.allowed" % name
|
||||
)
|
||||
if not self.authenticator.validate_username(name):
|
||||
raise ValueError("Token name %r is not valid" % name)
|
||||
raise ValueError("Token user name %r is not valid" % name)
|
||||
if kind == 'service':
|
||||
if not any(service["name"] == name for service in self.services):
|
||||
self.log.warning(
|
||||
@@ -2183,14 +2185,14 @@ class JupyterHub(Application):
|
||||
else:
|
||||
version_hash = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
|
||||
oauth_no_confirm_whitelist = set()
|
||||
oauth_no_confirm_list = set()
|
||||
for service in self._service_map.values():
|
||||
if service.oauth_no_confirm:
|
||||
self.log.warning(
|
||||
"Allowing service %s to complete OAuth without confirmation on an authorization web page",
|
||||
service.name,
|
||||
)
|
||||
oauth_no_confirm_whitelist.add(service.oauth_client_id)
|
||||
oauth_no_confirm_list.add(service.oauth_client_id)
|
||||
|
||||
settings = dict(
|
||||
log_function=log_request,
|
||||
@@ -2226,7 +2228,7 @@ class JupyterHub(Application):
|
||||
default_server_name=self._default_server_name,
|
||||
named_server_limit_per_user=self.named_server_limit_per_user,
|
||||
oauth_provider=self.oauth_provider,
|
||||
oauth_no_confirm_whitelist=oauth_no_confirm_whitelist,
|
||||
oauth_no_confirm_list=oauth_no_confirm_list,
|
||||
concurrent_spawn_limit=self.concurrent_spawn_limit,
|
||||
spawn_throttle_retry_range=self.spawn_throttle_retry_range,
|
||||
active_server_limit=self.active_server_limit,
|
||||
|
@@ -7,6 +7,7 @@ import re
|
||||
import sys
|
||||
import warnings
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import partial
|
||||
from shutil import which
|
||||
from subprocess import PIPE
|
||||
from subprocess import Popen
|
||||
@@ -100,41 +101,74 @@ class Authenticator(LoggingConfigurable):
|
||||
"""
|
||||
).tag(config=True)
|
||||
|
||||
whitelist = Set(
|
||||
whitelist = Set(help="Deprecated, use `Authenticator.allowed`", config=True,)
|
||||
|
||||
allowed = Set(
|
||||
help="""
|
||||
Whitelist of usernames that are allowed to log in.
|
||||
Set of usernames that are allowed to log in.
|
||||
|
||||
Use this with supported authenticators to restrict which users can log in. This is an
|
||||
additional whitelist that further restricts users, beyond whatever restrictions the
|
||||
additional list that further restricts users, beyond whatever restrictions the
|
||||
authenticator has in place.
|
||||
|
||||
If empty, does not perform any additional restriction.
|
||||
|
||||
.. versionchanged:: 1.2
|
||||
`Authenticator.whitelist` renamed to `allowed`
|
||||
"""
|
||||
).tag(config=True)
|
||||
|
||||
blacklist = Set(
|
||||
blocked = Set(
|
||||
help="""
|
||||
Blacklist of usernames that are not allowed to log in.
|
||||
Set of usernames that are not allowed to log in.
|
||||
|
||||
Use this with supported authenticators to restrict which users can not log in. This is an
|
||||
additional blacklist that further restricts users, beyond whatever restrictions the
|
||||
additional block list that further restricts users, beyond whatever restrictions the
|
||||
authenticator has in place.
|
||||
|
||||
If empty, does not perform any additional restriction.
|
||||
|
||||
.. versionadded: 0.9
|
||||
|
||||
.. versionchanged:: 1.2
|
||||
`Authenticator.blacklist` renamed to `blocked`
|
||||
"""
|
||||
).tag(config=True)
|
||||
|
||||
@observe('whitelist')
|
||||
def _check_whitelist(self, change):
|
||||
_deprecated_aliases = {
|
||||
"whitelist": ("allowed", "1.2"),
|
||||
"blacklist": ("blocked", "1.2"),
|
||||
}
|
||||
|
||||
@observe(*list(_deprecated_aliases))
|
||||
def _deprecated_trait(self, change):
|
||||
"""observer for deprecated traits"""
|
||||
old_attr = change.name
|
||||
new_attr, version = self._deprecated_aliases.get(old_attr)
|
||||
new_value = getattr(self, new_attr)
|
||||
if new_value != change.new:
|
||||
# only warn if different
|
||||
# protects backward-compatible config from warnings
|
||||
# if they set the same value under both names
|
||||
self.log.warning(
|
||||
"{cls}.{old} is deprecated in JupyterHub {version}, use {cls}.{new} instead".format(
|
||||
cls=self.__class__.__name__,
|
||||
old=old_attr,
|
||||
new=new_attr,
|
||||
version=version,
|
||||
)
|
||||
)
|
||||
setattr(self, new_attr, change.new)
|
||||
|
||||
@observe('allowed')
|
||||
def _check_allowed(self, change):
|
||||
short_names = [name for name in change['new'] if len(name) <= 1]
|
||||
if short_names:
|
||||
sorted_names = sorted(short_names)
|
||||
single = ''.join(sorted_names)
|
||||
string_set_typo = "set('%s')" % single
|
||||
self.log.warning(
|
||||
"whitelist contains single-character names: %s; did you mean set([%r]) instead of %s?",
|
||||
"Allowed list contains single-character names: %s; did you mean set([%r]) instead of %s?",
|
||||
sorted_names[:8],
|
||||
single,
|
||||
string_set_typo,
|
||||
@@ -260,6 +294,8 @@ class Authenticator(LoggingConfigurable):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# TODO: properly handle deprecated signature *and* name
|
||||
# with correct subclass override priority!
|
||||
for method_name in (
|
||||
'check_whitelist',
|
||||
'check_blacklist',
|
||||
@@ -326,39 +362,45 @@ class Authenticator(LoggingConfigurable):
|
||||
username = self.username_map.get(username, username)
|
||||
return username
|
||||
|
||||
def check_whitelist(self, username, authentication=None):
|
||||
"""Check if a username is allowed to authenticate based on whitelist configuration
|
||||
def check_allowed(self, username, authentication=None):
|
||||
"""Check if a username is allowed to authenticate based on configuration
|
||||
|
||||
Return True if username is allowed, False otherwise.
|
||||
No whitelist means any username is allowed.
|
||||
No allowed set means any username is allowed.
|
||||
|
||||
Names are normalized *before* being checked against the whitelist.
|
||||
Names are normalized *before* being checked against the allowed set.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
Signature updated to accept authentication data and any future changes
|
||||
"""
|
||||
if not self.whitelist:
|
||||
# No whitelist means any name is allowed
|
||||
return True
|
||||
return username in self.whitelist
|
||||
|
||||
def check_blacklist(self, username, authentication=None):
|
||||
"""Check if a username is blocked to authenticate based on blacklist configuration
|
||||
.. versionchanged:: 1.2
|
||||
Renamed check_whitelist to check_allowed
|
||||
"""
|
||||
if not self.allowed:
|
||||
# No allowed set means any name is allowed
|
||||
return True
|
||||
return username in self.allowed
|
||||
|
||||
def check_blocked(self, username, authentication=None):
|
||||
"""Check if a username is blocked to authenticate based on Authenticator.blocked configuration
|
||||
|
||||
Return True if username is allowed, False otherwise.
|
||||
No blacklist means any username is allowed.
|
||||
No block list means any username is allowed.
|
||||
|
||||
Names are normalized *before* being checked against the blacklist.
|
||||
Names are normalized *before* being checked against the block list.
|
||||
|
||||
.. versionadded: 0.9
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
Signature updated to accept authentication data as second argument
|
||||
|
||||
.. versionchanged:: 1.2
|
||||
Renamed check_blacklist to check_blocked
|
||||
"""
|
||||
if not self.blacklist:
|
||||
# No blacklist means any name is allowed
|
||||
if not self.blocked:
|
||||
# No block list means any name is allowed
|
||||
return True
|
||||
return username not in self.blacklist
|
||||
return username not in self.blocked
|
||||
|
||||
async def get_authenticated_user(self, handler, data):
|
||||
"""Authenticate the user who is attempting to log in
|
||||
@@ -367,7 +409,7 @@ class Authenticator(LoggingConfigurable):
|
||||
|
||||
This calls `authenticate`, which should be overridden in subclasses,
|
||||
normalizes the username if any normalization should be done,
|
||||
and then validates the name in the whitelist.
|
||||
and then validates the name in the allowed set.
|
||||
|
||||
This is the outer API for authenticating a user.
|
||||
Subclasses should not override this method.
|
||||
@@ -375,7 +417,7 @@ class Authenticator(LoggingConfigurable):
|
||||
The various stages can be overridden separately:
|
||||
- `authenticate` turns formdata into a username
|
||||
- `normalize_username` normalizes the username
|
||||
- `check_whitelist` checks against the user whitelist
|
||||
- `check_allowed` checks against the user allowed
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
return dict instead of username
|
||||
@@ -389,7 +431,7 @@ class Authenticator(LoggingConfigurable):
|
||||
else:
|
||||
authenticated = {'name': authenticated}
|
||||
authenticated.setdefault('auth_state', None)
|
||||
# Leave the default as None, but reevaluate later post-whitelist
|
||||
# Leave the default as None, but reevaluate later post-allowed-check
|
||||
authenticated.setdefault('admin', None)
|
||||
|
||||
# normalize the username
|
||||
@@ -400,20 +442,16 @@ class Authenticator(LoggingConfigurable):
|
||||
self.log.warning("Disallowing invalid username %r.", username)
|
||||
return
|
||||
|
||||
blacklist_pass = await maybe_future(
|
||||
self.check_blacklist(username, authenticated)
|
||||
)
|
||||
whitelist_pass = await maybe_future(
|
||||
self.check_whitelist(username, authenticated)
|
||||
)
|
||||
blocked_pass = await maybe_future(self.check_blocked(username, authenticated))
|
||||
allowed_pass = await maybe_future(self.check_allowed(username, authenticated))
|
||||
|
||||
if blacklist_pass:
|
||||
if blocked_pass:
|
||||
pass
|
||||
else:
|
||||
self.log.warning("User %r in blacklist. Stop authentication", username)
|
||||
self.log.warning("User %r blocked. Stop authentication", username)
|
||||
return
|
||||
|
||||
if whitelist_pass:
|
||||
if allowed_pass:
|
||||
if authenticated['admin'] is None:
|
||||
authenticated['admin'] = await maybe_future(
|
||||
self.is_admin(handler, authenticated)
|
||||
@@ -423,7 +461,7 @@ class Authenticator(LoggingConfigurable):
|
||||
|
||||
return authenticated
|
||||
else:
|
||||
self.log.warning("User %r not in whitelist.", username)
|
||||
self.log.warning("User %r not allowed.", username)
|
||||
return
|
||||
|
||||
async def refresh_user(self, user, handler=None):
|
||||
@@ -479,7 +517,7 @@ class Authenticator(LoggingConfigurable):
|
||||
It must return the username on successful authentication,
|
||||
and return None on failed authentication.
|
||||
|
||||
Checking the whitelist is handled separately by the caller.
|
||||
Checking allowed/blocked is handled separately by the caller.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
Allow `authenticate` to return a dict containing auth_state.
|
||||
@@ -520,10 +558,10 @@ class Authenticator(LoggingConfigurable):
|
||||
|
||||
This method may be a coroutine.
|
||||
|
||||
By default, this just adds the user to the whitelist.
|
||||
By default, this just adds the user to the allowed set.
|
||||
|
||||
Subclasses may do more extensive things, such as adding actual unix users,
|
||||
but they should call super to ensure the whitelist is updated.
|
||||
but they should call super to ensure the allowed set is updated.
|
||||
|
||||
Note that this should be idempotent, since it is called whenever the hub restarts
|
||||
for all users.
|
||||
@@ -533,19 +571,19 @@ class Authenticator(LoggingConfigurable):
|
||||
"""
|
||||
if not self.validate_username(user.name):
|
||||
raise ValueError("Invalid username: %s" % user.name)
|
||||
if self.whitelist:
|
||||
self.whitelist.add(user.name)
|
||||
if self.allowed:
|
||||
self.allowed.add(user.name)
|
||||
|
||||
def delete_user(self, user):
|
||||
"""Hook called when a user is deleted
|
||||
|
||||
Removes the user from the whitelist.
|
||||
Subclasses should call super to ensure the whitelist is updated.
|
||||
Removes the user from the allowed set.
|
||||
Subclasses should call super to ensure the allowed set is updated.
|
||||
|
||||
Args:
|
||||
user (User): The User wrapper object
|
||||
"""
|
||||
self.whitelist.discard(user.name)
|
||||
self.allowed.discard(user.name)
|
||||
|
||||
auto_login = Bool(
|
||||
False,
|
||||
@@ -610,6 +648,38 @@ class Authenticator(LoggingConfigurable):
|
||||
return [('/login', LoginHandler)]
|
||||
|
||||
|
||||
def _deprecated_method(old_name, new_name, version, self, *args, **kwargs):
|
||||
"""Method wrapper for a deprecated method name"""
|
||||
|
||||
warnings.warn(
|
||||
(
|
||||
"{cls}.{old_name} is deprecated in JupyterHub {version}."
|
||||
" Please use {cls}.{new_name} instead."
|
||||
).format(
|
||||
cls=self.__class__.__name__,
|
||||
old_name=old_name,
|
||||
new_name=new_name,
|
||||
version=version,
|
||||
),
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
old_method = getattr(self, new_name)
|
||||
return old_method(*args, **kwargs)
|
||||
|
||||
|
||||
# deprecate white/blacklist method names
|
||||
for _old_name, _new_name, _version in [
|
||||
("check_whitelist", "check_allowed", "1.2"),
|
||||
("check_blacklist", "check_blocked", "1.2"),
|
||||
]:
|
||||
setattr(
|
||||
Authenticator,
|
||||
_old_name,
|
||||
partial(_deprecated_method, _old_name, _new_name, _version),
|
||||
)
|
||||
|
||||
|
||||
class LocalAuthenticator(Authenticator):
|
||||
"""Base class for Authenticators that work with local Linux/UNIX users
|
||||
|
||||
@@ -669,37 +739,37 @@ class LocalAuthenticator(Authenticator):
|
||||
"""
|
||||
).tag(config=True)
|
||||
|
||||
group_whitelist = Set(
|
||||
help="""
|
||||
Whitelist all users from this UNIX group.
|
||||
group_whitelist = Set(help="""DEPRECATED: use allowed_groups""",).tag(config=True)
|
||||
|
||||
This makes the username whitelist ineffective.
|
||||
allowed_groups = Set(
|
||||
help="""
|
||||
Allow login from all users in these UNIX groups.
|
||||
|
||||
If set, allowed username set is ignored.
|
||||
"""
|
||||
).tag(config=True)
|
||||
|
||||
@observe('group_whitelist')
|
||||
def _group_whitelist_changed(self, change):
|
||||
"""
|
||||
Log a warning if both group_whitelist and user whitelist are set.
|
||||
"""
|
||||
if self.whitelist:
|
||||
@observe('allowed_groups')
|
||||
def _allowed_groups_changed(self, change):
|
||||
"""Log a warning if mutually exclusive user and group allowed sets are specified."""
|
||||
if self.allowed:
|
||||
self.log.warning(
|
||||
"Ignoring username whitelist because group whitelist supplied!"
|
||||
"Ignoring Authenticator.allowed set because Authenticator.allowed_groups supplied!"
|
||||
)
|
||||
|
||||
def check_whitelist(self, username, authentication=None):
|
||||
if self.group_whitelist:
|
||||
return self.check_group_whitelist(username, authentication)
|
||||
def check_allowed(self, username, authentication=None):
|
||||
if self.allowed_groups:
|
||||
return self.check_allowed_groups(username, authentication)
|
||||
else:
|
||||
return super().check_whitelist(username, authentication)
|
||||
return super().check_allowed(username, authentication)
|
||||
|
||||
def check_group_whitelist(self, username, authentication=None):
|
||||
def check_allowed_groups(self, username, authentication=None):
|
||||
"""
|
||||
If group_whitelist is configured, check if authenticating user is part of group.
|
||||
If allowed_groups is configured, check if authenticating user is part of group.
|
||||
"""
|
||||
if not self.group_whitelist:
|
||||
if not self.allowed_groups:
|
||||
return False
|
||||
for grnam in self.group_whitelist:
|
||||
for grnam in self.allowed_groups:
|
||||
try:
|
||||
group = self._getgrnam(grnam)
|
||||
except KeyError:
|
||||
@@ -843,7 +913,7 @@ class PAMAuthenticator(LocalAuthenticator):
|
||||
Authoritative list of user groups that determine admin access.
|
||||
Users not in these groups can still be granted admin status through admin_users.
|
||||
|
||||
White/blacklisting rules still apply.
|
||||
allowed/blocked rules still apply.
|
||||
"""
|
||||
).tag(config=True)
|
||||
|
||||
@@ -986,6 +1056,16 @@ class PAMAuthenticator(LocalAuthenticator):
|
||||
return super().normalize_username(username)
|
||||
|
||||
|
||||
for _old_name, _new_name, _version in [
|
||||
("check_group_whitelist", "check_group_allowed", "1.2"),
|
||||
]:
|
||||
setattr(
|
||||
LocalAuthenticator,
|
||||
_old_name,
|
||||
partial(_deprecated_method, _old_name, _new_name, _version),
|
||||
)
|
||||
|
||||
|
||||
class DummyAuthenticator(Authenticator):
|
||||
"""Dummy Authenticator for testing
|
||||
|
||||
|
@@ -860,15 +860,15 @@ class HubAuthenticated(object):
|
||||
if kind == 'service':
|
||||
# it's a service, check hub_services
|
||||
if self.hub_services and name in self.hub_services:
|
||||
app_log.debug("Allowing whitelisted Hub service %s", name)
|
||||
app_log.debug("Allowing Hub service %s", name)
|
||||
return model
|
||||
else:
|
||||
app_log.warning("Not allowing Hub service %s", name)
|
||||
raise UserNotAllowed(model)
|
||||
|
||||
if self.hub_users and name in self.hub_users:
|
||||
# user in whitelist
|
||||
app_log.debug("Allowing whitelisted Hub user %s", name)
|
||||
# user in allowed list
|
||||
app_log.debug("Allowing Hub user %s", name)
|
||||
return model
|
||||
elif self.hub_groups and set(model['groups']).intersection(self.hub_groups):
|
||||
allowed_groups = set(model['groups']).intersection(self.hub_groups)
|
||||
@@ -877,7 +877,7 @@ class HubAuthenticated(object):
|
||||
name,
|
||||
','.join(sorted(allowed_groups)),
|
||||
)
|
||||
# group in whitelist
|
||||
# group in allowed list
|
||||
return model
|
||||
else:
|
||||
app_log.warning("Not allowing Hub user %s", name)
|
||||
|
@@ -435,9 +435,9 @@ class Spawner(LoggingConfigurable):
|
||||
'LC_ALL',
|
||||
],
|
||||
help="""
|
||||
Whitelist of environment variables for the single-user server to inherit from the JupyterHub process.
|
||||
List of environment variables for the single-user server to inherit from the JupyterHub process.
|
||||
|
||||
This whitelist is used to ensure that sensitive information in the JupyterHub process's environment
|
||||
This list is used to ensure that sensitive information in the JupyterHub process's environment
|
||||
(such as `CONFIGPROXY_AUTH_TOKEN`) is not passed to the single-user server's process.
|
||||
""",
|
||||
).tag(config=True)
|
||||
@@ -456,7 +456,7 @@ class Spawner(LoggingConfigurable):
|
||||
|
||||
Environment variables that end up in the single-user server's process come from 3 sources:
|
||||
- This `environment` configurable
|
||||
- The JupyterHub process' environment variables that are whitelisted in `env_keep`
|
||||
- The JupyterHub process' environment variables that are listed in `env_keep`
|
||||
- Variables to establish contact between the single-user notebook and the hub (such as JUPYTERHUB_API_TOKEN)
|
||||
|
||||
The `environment` configurable should be set by JupyterHub administrators to add
|
||||
|
@@ -93,7 +93,7 @@ def test_generate_config():
|
||||
os.remove(cfg_file)
|
||||
assert cfg_file in out
|
||||
assert 'Spawner.cmd' in cfg_text
|
||||
assert 'Authenticator.whitelist' in cfg_text
|
||||
assert 'Authenticator.allowed' in cfg_text
|
||||
|
||||
|
||||
async def test_init_tokens(request):
|
||||
|
@@ -6,6 +6,7 @@ from unittest import mock
|
||||
|
||||
import pytest
|
||||
from requests import HTTPError
|
||||
from traitlets.config import Config
|
||||
|
||||
from .mocking import MockPAMAuthenticator
|
||||
from .mocking import MockStructGroup
|
||||
@@ -137,8 +138,8 @@ async def test_pam_auth_admin_groups():
|
||||
assert authorized['admin'] is False
|
||||
|
||||
|
||||
async def test_pam_auth_whitelist():
|
||||
authenticator = MockPAMAuthenticator(whitelist={'wash', 'kaylee'})
|
||||
async def test_pam_auth_allowed():
|
||||
authenticator = MockPAMAuthenticator(allowed={'wash', 'kaylee'})
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
None, {'username': 'kaylee', 'password': 'kaylee'}
|
||||
)
|
||||
@@ -155,11 +156,11 @@ async def test_pam_auth_whitelist():
|
||||
assert authorized is None
|
||||
|
||||
|
||||
async def test_pam_auth_group_whitelist():
|
||||
async def test_pam_auth_allowed_groups():
|
||||
def getgrnam(name):
|
||||
return MockStructGroup('grp', ['kaylee'])
|
||||
|
||||
authenticator = MockPAMAuthenticator(group_whitelist={'group'})
|
||||
authenticator = MockPAMAuthenticator(allowed_groups={'group'})
|
||||
|
||||
with mock.patch.object(authenticator, '_getgrnam', getgrnam):
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
@@ -174,7 +175,7 @@ async def test_pam_auth_group_whitelist():
|
||||
assert authorized is None
|
||||
|
||||
|
||||
async def test_pam_auth_blacklist():
|
||||
async def test_pam_auth_blocked():
|
||||
# Null case compared to next case
|
||||
authenticator = MockPAMAuthenticator()
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
@@ -183,50 +184,41 @@ async def test_pam_auth_blacklist():
|
||||
assert authorized['name'] == 'wash'
|
||||
|
||||
# Blacklist basics
|
||||
authenticator = MockPAMAuthenticator(blacklist={'wash'})
|
||||
authenticator = MockPAMAuthenticator(blocked={'wash'})
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
None, {'username': 'wash', 'password': 'wash'}
|
||||
)
|
||||
assert authorized is None
|
||||
|
||||
# User in both white and blacklists: default deny. Make error someday?
|
||||
authenticator = MockPAMAuthenticator(
|
||||
blacklist={'wash'}, whitelist={'wash', 'kaylee'}
|
||||
)
|
||||
# User in both allowed and blocked: default deny. Make error someday?
|
||||
authenticator = MockPAMAuthenticator(blocked={'wash'}, allowed={'wash', 'kaylee'})
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
None, {'username': 'wash', 'password': 'wash'}
|
||||
)
|
||||
assert authorized is None
|
||||
|
||||
# User not in blacklist can log in
|
||||
authenticator = MockPAMAuthenticator(
|
||||
blacklist={'wash'}, whitelist={'wash', 'kaylee'}
|
||||
)
|
||||
# User not in blocked set can log in
|
||||
authenticator = MockPAMAuthenticator(blocked={'wash'}, allowed={'wash', 'kaylee'})
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
None, {'username': 'kaylee', 'password': 'kaylee'}
|
||||
)
|
||||
assert authorized['name'] == 'kaylee'
|
||||
|
||||
# User in whitelist, blacklist irrelevent
|
||||
authenticator = MockPAMAuthenticator(
|
||||
blacklist={'mal'}, whitelist={'wash', 'kaylee'}
|
||||
)
|
||||
# User in allowed, blocked irrelevent
|
||||
authenticator = MockPAMAuthenticator(blocked={'mal'}, allowed={'wash', 'kaylee'})
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
None, {'username': 'wash', 'password': 'wash'}
|
||||
)
|
||||
assert authorized['name'] == 'wash'
|
||||
|
||||
# User in neither list
|
||||
authenticator = MockPAMAuthenticator(
|
||||
blacklist={'mal'}, whitelist={'wash', 'kaylee'}
|
||||
)
|
||||
authenticator = MockPAMAuthenticator(blocked={'mal'}, allowed={'wash', 'kaylee'})
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
None, {'username': 'simon', 'password': 'simon'}
|
||||
)
|
||||
assert authorized is None
|
||||
|
||||
# blacklist == {}
|
||||
authenticator = MockPAMAuthenticator(blacklist=set(), whitelist={'wash', 'kaylee'})
|
||||
authenticator = MockPAMAuthenticator(blocked=set(), allowed={'wash', 'kaylee'})
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
None, {'username': 'kaylee', 'password': 'kaylee'}
|
||||
)
|
||||
@@ -253,7 +245,7 @@ async def test_deprecated_signatures():
|
||||
|
||||
|
||||
async def test_pam_auth_no_such_group():
|
||||
authenticator = MockPAMAuthenticator(group_whitelist={'nosuchcrazygroup'})
|
||||
authenticator = MockPAMAuthenticator(allowed_groups={'nosuchcrazygroup'})
|
||||
authorized = await authenticator.get_authenticated_user(
|
||||
None, {'username': 'kaylee', 'password': 'kaylee'}
|
||||
)
|
||||
@@ -262,7 +254,7 @@ async def test_pam_auth_no_such_group():
|
||||
|
||||
async def test_wont_add_system_user():
|
||||
user = orm.User(name='lioness4321')
|
||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||
authenticator = auth.PAMAuthenticator(allowed={'mal'})
|
||||
authenticator.create_system_users = False
|
||||
with pytest.raises(KeyError):
|
||||
await authenticator.add_user(user)
|
||||
@@ -270,7 +262,7 @@ async def test_wont_add_system_user():
|
||||
|
||||
async def test_cant_add_system_user():
|
||||
user = orm.User(name='lioness4321')
|
||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||
authenticator = auth.PAMAuthenticator(allowed={'mal'})
|
||||
authenticator.add_user_cmd = ['jupyterhub-fake-command']
|
||||
authenticator.create_system_users = True
|
||||
|
||||
@@ -296,7 +288,7 @@ async def test_cant_add_system_user():
|
||||
|
||||
async def test_add_system_user():
|
||||
user = orm.User(name='lioness4321')
|
||||
authenticator = auth.PAMAuthenticator(whitelist={'mal'})
|
||||
authenticator = auth.PAMAuthenticator(allowed={'mal'})
|
||||
authenticator.create_system_users = True
|
||||
authenticator.add_user_cmd = ['echo', '/home/USERNAME']
|
||||
|
||||
@@ -317,13 +309,13 @@ async def test_add_system_user():
|
||||
|
||||
async def test_delete_user():
|
||||
user = orm.User(name='zoe')
|
||||
a = MockPAMAuthenticator(whitelist={'mal'})
|
||||
a = MockPAMAuthenticator(allowed={'mal'})
|
||||
|
||||
assert 'zoe' not in a.whitelist
|
||||
assert 'zoe' not in a.allowed
|
||||
await a.add_user(user)
|
||||
assert 'zoe' in a.whitelist
|
||||
assert 'zoe' in a.allowed
|
||||
a.delete_user(user)
|
||||
assert 'zoe' not in a.whitelist
|
||||
assert 'zoe' not in a.allowed
|
||||
|
||||
|
||||
def test_urls():
|
||||
@@ -461,3 +453,10 @@ async def test_post_auth_hook():
|
||||
)
|
||||
|
||||
assert authorized['testkey'] == 'testvalue'
|
||||
|
||||
|
||||
async def test_deprecations():
|
||||
cfg = Config()
|
||||
cfg.Authenticator.whitelist = {'user'}
|
||||
authenticator = auth.Authenticator(config=cfg)
|
||||
assert authenticator.allowed == {'user'}
|
||||
|
@@ -686,7 +686,7 @@ async def test_shutdown_on_logout(app, shutdown_on_logout):
|
||||
assert spawner.ready == (not shutdown_on_logout)
|
||||
|
||||
|
||||
async def test_login_no_whitelist_adds_user(app):
|
||||
async def test_login_no_allowed_adds_user(app):
|
||||
auth = app.authenticator
|
||||
mock_add_user = mock.Mock()
|
||||
with mock.patch.object(auth, 'add_user', mock_add_user):
|
||||
|
@@ -185,7 +185,7 @@ def test_hub_authenticated(request):
|
||||
|
||||
m.get(good_url, text=json.dumps(mock_model))
|
||||
|
||||
# no whitelist
|
||||
# no specific allowed user
|
||||
r = requests.get(
|
||||
'http://127.0.0.1:%i' % port,
|
||||
cookies={'jubal': 'early'},
|
||||
@@ -194,7 +194,7 @@ def test_hub_authenticated(request):
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
|
||||
# pass whitelist
|
||||
# pass allowed user
|
||||
TestHandler.hub_users = {'jubalearly'}
|
||||
r = requests.get(
|
||||
'http://127.0.0.1:%i' % port,
|
||||
@@ -204,7 +204,7 @@ def test_hub_authenticated(request):
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
|
||||
# no pass whitelist
|
||||
# no pass allowed ser
|
||||
TestHandler.hub_users = {'kaylee'}
|
||||
r = requests.get(
|
||||
'http://127.0.0.1:%i' % port,
|
||||
@@ -213,7 +213,7 @@ def test_hub_authenticated(request):
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
# pass group whitelist
|
||||
# pass allowed group
|
||||
TestHandler.hub_groups = {'lions'}
|
||||
r = requests.get(
|
||||
'http://127.0.0.1:%i' % port,
|
||||
@@ -223,7 +223,7 @@ def test_hub_authenticated(request):
|
||||
r.raise_for_status()
|
||||
assert r.status_code == 200
|
||||
|
||||
# no pass group whitelist
|
||||
# no pass allowed group
|
||||
TestHandler.hub_groups = {'tigers'}
|
||||
r = requests.get(
|
||||
'http://127.0.0.1:%i' % port,
|
||||
|
Reference in New Issue
Block a user