call it allowed_users

be clearer since it's users vs groups, etc.
This commit is contained in:
Min RK
2020-06-24 13:29:42 +02:00
parent 7ad4b0c7cb
commit 6e988bf587
6 changed files with 85 additions and 72 deletions

View File

@@ -7,20 +7,20 @@ with an account and password on the system will be allowed to login.
## Create a set of allowed users
You can restrict which users are allowed to login with a set,
`Authenticator.allowed`:
`Authenticator.allowed_users`:
```python
c.Authenticator.allowed = {'mal', 'zoe', 'inara', 'kaylee'}
c.Authenticator.allowed_users = {'mal', 'zoe', 'inara', 'kaylee'}
```
Users in the allowed set are added to the Hub database when the Hub is
Users in the `allowed_users` set are added to the Hub database when the Hub is
started.
## Configure admins (`admin_users`)
Admin users of JupyterHub, `admin_users`, can add and remove users from
the user `allowed` set. `admin_users` can take actions on other users'
the user `allowed_users` set. `admin_users` can take actions on other users'
behalf, such as stopping and restarting their servers.
A set of initial admin users, `admin_users` can configured be as follows:
@@ -28,7 +28,7 @@ A set of initial admin users, `admin_users` can configured be as follows:
```python
c.Authenticator.admin_users = {'mal', 'zoe'}
```
Users in the admin set are automatically added to the user `allowed` set,
Users in the admin set are automatically added to the user `allowed_users` set,
if they are not already present.
Each authenticator may have different ways of determining whether a user is an
@@ -53,12 +53,12 @@ sure your users know if admin_access is enabled.**
Users can be added to and removed from the Hub via either the admin
panel or the REST API. When a user is **added**, the user will be
automatically added to the allowed set and database. Restarting the Hub
will not require manually updating the allowed set in your config file,
automatically added to the allowed users set and database. Restarting the Hub
will not require manually updating the allowed users set in your config file,
as the users will be loaded from the database.
After starting the Hub once, it is not sufficient to **remove** a user
from the allowed set in your config file. You must also remove the user
from the allowed users set in your config file. You must also remove the user
from the Hub's database, either by deleting the user from JupyterHub's
admin page, or you can clear the `jupyterhub.sqlite` database and start
fresh.

View File

@@ -52,7 +52,7 @@ c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL']
c.LocalAuthenticator.create_system_users = True
# specify users and admin
c.Authenticator.allowed = {'rgbkrk', 'minrk', 'jhamrick'}
c.Authenticator.allowed_users = {'rgbkrk', 'minrk', 'jhamrick'}
c.Authenticator.admin_users = {'jhamrick', 'rgbkrk'}
# uses the default spawner

View File

@@ -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.
allowed = [
allowed_users = [
self.authenticator.normalize_username(name)
for name in self.authenticator.allowed
for name in self.authenticator.allowed_users
]
self.authenticator.allowed = set(allowed) # force normalization
for username in allowed:
self.authenticator.allowed_users = set(allowed_users) # force normalization
for username in allowed_users:
if not self.authenticator.validate_username(username):
raise ValueError("username %r is not valid" % username)
if not allowed:
if not allowed_users:
self.log.info(
"Not using allowed user list. Any authenticated user will be allowed."
"Not using allowed_users. Any authenticated user will be allowed."
)
# add allowed users to the db
for name in allowed:
for name in allowed_users:
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 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,
# This ensures Authenticator.allowed_users is up-to-date with the database.
# This lets .allowed_users be used to set up initial list,
# but changes to the allowed_users set 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 allowed set and the users in the db are now the same.
# The allowed_users set and the users in the db are now the same.
# From this point on, any user changes should be done simultaneously
# to the allowed set and user db, unless the allowed set is empty (all users allowed).
# to the allowed_users set and user db, unless the allowed set is empty (all users allowed).
TOTAL_USERS.set(total_users)
@@ -1773,7 +1773,7 @@ class JupyterHub(Application):
await maybe_future(self.authenticator.check_allowed(username, None))
):
raise ValueError(
"Username %r is not in Authenticator.allowed" % username
"Username %r is not in Authenticator.allowed_users" % username
)
user = orm.User.find(db, name=username)
if user is None:
@@ -1801,7 +1801,8 @@ class JupyterHub(Application):
await maybe_future(self.authenticator.check_allowed(name, None))
):
raise ValueError(
"Token user name %r is not in Authenticator.allowed" % name
"Token user name %r is not in Authenticator.allowed_users"
% name
)
if not self.authenticator.validate_username(name):
raise ValueError("Token user name %r is not valid" % name)

View File

@@ -101,9 +101,9 @@ class Authenticator(LoggingConfigurable):
"""
).tag(config=True)
whitelist = Set(help="Deprecated, use `Authenticator.allowed`", config=True,)
whitelist = Set(help="Deprecated, use `Authenticator.allowed_users`", config=True,)
allowed = Set(
allowed_users = Set(
help="""
Set of usernames that are allowed to log in.
@@ -114,11 +114,11 @@ class Authenticator(LoggingConfigurable):
If empty, does not perform any additional restriction.
.. versionchanged:: 1.2
`Authenticator.whitelist` renamed to `allowed`
`Authenticator.whitelist` renamed to `allowed_users`
"""
).tag(config=True)
blocked = Set(
blocked_users = Set(
help="""
Set of usernames that are not allowed to log in.
@@ -131,13 +131,13 @@ class Authenticator(LoggingConfigurable):
.. versionadded: 0.9
.. versionchanged:: 1.2
`Authenticator.blacklist` renamed to `blocked`
`Authenticator.blacklist` renamed to `blocked_users`
"""
).tag(config=True)
_deprecated_aliases = {
"whitelist": ("allowed", "1.2"),
"blacklist": ("blocked", "1.2"),
"whitelist": ("allowed_users", "1.2"),
"blacklist": ("blocked_users", "1.2"),
}
@observe(*list(_deprecated_aliases))
@@ -160,15 +160,15 @@ class Authenticator(LoggingConfigurable):
)
setattr(self, new_attr, change.new)
@observe('allowed')
def _check_allowed(self, change):
@observe('allowed_users')
def _check_allowed_users(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(
"Allowed list contains single-character names: %s; did you mean set([%r]) instead of %s?",
"Allowed set contains single-character names: %s; did you mean set([%r]) instead of %s?",
sorted_names[:8],
single,
string_set_typo,
@@ -301,7 +301,7 @@ class Authenticator(LoggingConfigurable):
# with correct subclass override priority!
for old_name, new_name in (
('check_whitelist', 'check_allowed'),
('check_blacklist', 'check_blocked'),
('check_blacklist', 'check_blocked_users'),
('check_group_whitelist', 'check_allowed_groups'),
):
old_method = getattr(self, old_name, None)
@@ -399,7 +399,7 @@ class Authenticator(LoggingConfigurable):
"""Check if a username is allowed to authenticate based on configuration
Return True if username is allowed, False otherwise.
No allowed set means any username is allowed.
No allowed_users set means any username is allowed.
Names are normalized *before* being checked against the allowed set.
@@ -409,12 +409,12 @@ class Authenticator(LoggingConfigurable):
.. versionchanged:: 1.2
Renamed check_whitelist to check_allowed
"""
if not self.allowed:
if not self.allowed_users:
# No allowed set means any name is allowed
return True
return username in self.allowed
return username in self.allowed_users
def check_blocked(self, username, authentication=None):
def check_blocked_users(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.
@@ -428,12 +428,12 @@ class Authenticator(LoggingConfigurable):
Signature updated to accept authentication data as second argument
.. versionchanged:: 1.2
Renamed check_blacklist to check_blocked
Renamed check_blacklist to check_blocked_users
"""
if not self.blocked:
if not self.blocked_users:
# No block list means any name is allowed
return True
return username not in self.blocked
return username not in self.blocked_users
async def get_authenticated_user(self, handler, data):
"""Authenticate the user who is attempting to log in
@@ -450,7 +450,7 @@ class Authenticator(LoggingConfigurable):
The various stages can be overridden separately:
- `authenticate` turns formdata into a username
- `normalize_username` normalizes the username
- `check_allowed` checks against the user allowed
- `check_allowed` checks against the allowed usernames
.. versionchanged:: 0.8
return dict instead of username
@@ -475,7 +475,9 @@ class Authenticator(LoggingConfigurable):
self.log.warning("Disallowing invalid username %r.", username)
return
blocked_pass = await maybe_future(self.check_blocked(username, authenticated))
blocked_pass = await maybe_future(
self.check_blocked_users(username, authenticated)
)
allowed_pass = await maybe_future(self.check_allowed(username, authenticated))
if blocked_pass:
@@ -550,7 +552,7 @@ class Authenticator(LoggingConfigurable):
It must return the username on successful authentication,
and return None on failed authentication.
Checking allowed/blocked is handled separately by the caller.
Checking allowed_users/blocked_users is handled separately by the caller.
.. versionchanged:: 0.8
Allow `authenticate` to return a dict containing auth_state.
@@ -591,10 +593,10 @@ class Authenticator(LoggingConfigurable):
This method may be a coroutine.
By default, this just adds the user to the allowed set.
By default, this just adds the user to the allowed_users set.
Subclasses may do more extensive things, such as adding actual unix users,
but they should call super to ensure the allowed set is updated.
but they should call super to ensure the allowed_users set is updated.
Note that this should be idempotent, since it is called whenever the hub restarts
for all users.
@@ -604,19 +606,19 @@ class Authenticator(LoggingConfigurable):
"""
if not self.validate_username(user.name):
raise ValueError("Invalid username: %s" % user.name)
if self.allowed:
self.allowed.add(user.name)
if self.allowed_users:
self.allowed_users.add(user.name)
def delete_user(self, user):
"""Hook called when a user is deleted
Removes the user from the allowed set.
Subclasses should call super to ensure the allowed set is updated.
Removes the user from the allowed_users set.
Subclasses should call super to ensure the allowed_users set is updated.
Args:
user (User): The User wrapper object
"""
self.allowed.discard(user.name)
self.allowed_users.discard(user.name)
auto_login = Bool(
False,
@@ -709,7 +711,7 @@ import types
# deprecate white/blacklist method names
for _old_name, _new_name, _version in [
("check_whitelist", "check_allowed", "1.2"),
("check_blacklist", "check_blocked", "1.2"),
("check_blacklist", "check_blocked_users", "1.2"),
]:
setattr(
Authenticator, _old_name, _deprecated_method(_old_name, _new_name, _version),
@@ -788,9 +790,9 @@ class LocalAuthenticator(Authenticator):
@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:
if self.allowed_users:
self.log.warning(
"Ignoring Authenticator.allowed set because Authenticator.allowed_groups supplied!"
"Ignoring Authenticator.allowed_users set because Authenticator.allowed_groups supplied!"
)
def check_allowed(self, username, authentication=None):

View File

@@ -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.allowed' in cfg_text
assert 'Authenticator.allowed_users' in cfg_text
async def test_init_tokens(request):

View File

@@ -141,7 +141,7 @@ async def test_pam_auth_admin_groups():
async def test_pam_auth_allowed():
authenticator = MockPAMAuthenticator(allowed={'wash', 'kaylee'})
authenticator = MockPAMAuthenticator(allowed_users={'wash', 'kaylee'})
authorized = await authenticator.get_authenticated_user(
None, {'username': 'kaylee', 'password': 'kaylee'}
)
@@ -186,41 +186,51 @@ async def test_pam_auth_blocked():
assert authorized['name'] == 'wash'
# Blacklist basics
authenticator = MockPAMAuthenticator(blocked={'wash'})
authenticator = MockPAMAuthenticator(blocked_users={'wash'})
authorized = await authenticator.get_authenticated_user(
None, {'username': 'wash', 'password': 'wash'}
)
assert authorized is None
# User in both allowed and blocked: default deny. Make error someday?
authenticator = MockPAMAuthenticator(blocked={'wash'}, allowed={'wash', 'kaylee'})
authenticator = MockPAMAuthenticator(
blocked_users={'wash'}, allowed_users={'wash', 'kaylee'}
)
authorized = await authenticator.get_authenticated_user(
None, {'username': 'wash', 'password': 'wash'}
)
assert authorized is None
# User not in blocked set can log in
authenticator = MockPAMAuthenticator(blocked={'wash'}, allowed={'wash', 'kaylee'})
authenticator = MockPAMAuthenticator(
blocked_users={'wash'}, allowed_users={'wash', 'kaylee'}
)
authorized = await authenticator.get_authenticated_user(
None, {'username': 'kaylee', 'password': 'kaylee'}
)
assert authorized['name'] == 'kaylee'
# User in allowed, blocked irrelevent
authenticator = MockPAMAuthenticator(blocked={'mal'}, allowed={'wash', 'kaylee'})
authenticator = MockPAMAuthenticator(
blocked_users={'mal'}, allowed_users={'wash', 'kaylee'}
)
authorized = await authenticator.get_authenticated_user(
None, {'username': 'wash', 'password': 'wash'}
)
assert authorized['name'] == 'wash'
# User in neither list
authenticator = MockPAMAuthenticator(blocked={'mal'}, allowed={'wash', 'kaylee'})
authenticator = MockPAMAuthenticator(
blocked_users={'mal'}, allowed_users={'wash', 'kaylee'}
)
authorized = await authenticator.get_authenticated_user(
None, {'username': 'simon', 'password': 'simon'}
)
assert authorized is None
authenticator = MockPAMAuthenticator(blocked=set(), allowed={'wash', 'kaylee'})
authenticator = MockPAMAuthenticator(
blocked_users=set(), allowed_users={'wash', 'kaylee'}
)
authorized = await authenticator.get_authenticated_user(
None, {'username': 'kaylee', 'password': 'kaylee'}
)
@@ -256,7 +266,7 @@ async def test_pam_auth_no_such_group():
async def test_wont_add_system_user():
user = orm.User(name='lioness4321')
authenticator = auth.PAMAuthenticator(allowed={'mal'})
authenticator = auth.PAMAuthenticator(allowed_users={'mal'})
authenticator.create_system_users = False
with pytest.raises(KeyError):
await authenticator.add_user(user)
@@ -264,7 +274,7 @@ async def test_wont_add_system_user():
async def test_cant_add_system_user():
user = orm.User(name='lioness4321')
authenticator = auth.PAMAuthenticator(allowed={'mal'})
authenticator = auth.PAMAuthenticator(allowed_users={'mal'})
authenticator.add_user_cmd = ['jupyterhub-fake-command']
authenticator.create_system_users = True
@@ -290,7 +300,7 @@ async def test_cant_add_system_user():
async def test_add_system_user():
user = orm.User(name='lioness4321')
authenticator = auth.PAMAuthenticator(allowed={'mal'})
authenticator = auth.PAMAuthenticator(allowed_users={'mal'})
authenticator.create_system_users = True
authenticator.add_user_cmd = ['echo', '/home/USERNAME']
@@ -311,13 +321,13 @@ async def test_add_system_user():
async def test_delete_user():
user = orm.User(name='zoe')
a = MockPAMAuthenticator(allowed={'mal'})
a = MockPAMAuthenticator(allowed_users={'mal'})
assert 'zoe' not in a.allowed
assert 'zoe' not in a.allowed_users
await a.add_user(user)
assert 'zoe' in a.allowed
assert 'zoe' in a.allowed_users
a.delete_user(user)
assert 'zoe' not in a.allowed
assert 'zoe' not in a.allowed_users
def test_urls():
@@ -472,10 +482,10 @@ def test_deprecated_config(caplog):
log.name,
logging.WARNING,
'Authenticator.whitelist is deprecated in JupyterHub 1.2, use '
'Authenticator.allowed instead',
'Authenticator.allowed_users instead',
)
]
assert authenticator.allowed == {'user'}
assert authenticator.allowed_users == {'user'}
def test_deprecated_methods():
@@ -496,7 +506,7 @@ def test_deprecated_config_subclass():
cfg.MyAuthenticator.whitelist = {'user'}
with pytest.deprecated_call():
authenticator = MyAuthenticator(config=cfg)
assert authenticator.allowed == {'user'}
assert authenticator.allowed_users == {'user'}
def test_deprecated_methods_subclass():