mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-17 15:03:02 +00:00
Allow Authenticators to return an admin
flag for users.
This commit is contained in:
@@ -235,6 +235,7 @@ class Authenticator(LoggingConfigurable):
|
|||||||
'name': authenticated,
|
'name': authenticated,
|
||||||
}
|
}
|
||||||
authenticated.setdefault('auth_state', None)
|
authenticated.setdefault('auth_state', None)
|
||||||
|
authenticated.setdefault('admin', None)
|
||||||
|
|
||||||
# normalize the username
|
# normalize the username
|
||||||
authenticated['name'] = username = self.normalize_username(authenticated['name'])
|
authenticated['name'] = username = self.normalize_username(authenticated['name'])
|
||||||
@@ -269,10 +270,10 @@ class Authenticator(LoggingConfigurable):
|
|||||||
Returns:
|
Returns:
|
||||||
user (str or dict or None): The username of the authenticated user,
|
user (str or dict or None): The username of the authenticated user,
|
||||||
or None if Authentication failed.
|
or None if Authentication failed.
|
||||||
If the Authenticator has state associated with the user,
|
The Authenticator may return a dict instead, which MUST have a
|
||||||
it can return a dict with the keys 'name' and 'auth_state',
|
key 'name' holding the username, and may have two optional keys
|
||||||
where 'name' is the username and 'auth_state' is a dictionary
|
set - 'auth_state', a dictionary of of auth state that will be
|
||||||
of auth state that will be persisted.
|
persisted; and 'admin', the admin setting value for the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def pre_spawn_start(self, user, spawner):
|
def pre_spawn_start(self, user, spawner):
|
||||||
|
@@ -336,7 +336,11 @@ class BaseHandler(RequestHandler):
|
|||||||
if authenticated:
|
if authenticated:
|
||||||
username = authenticated['name']
|
username = authenticated['name']
|
||||||
auth_state = authenticated.get('auth_state')
|
auth_state = authenticated.get('auth_state')
|
||||||
|
admin = authenticated.get('admin')
|
||||||
user = self.user_from_username(username)
|
user = self.user_from_username(username)
|
||||||
|
# Only set `admin` if the authenticator returned an explicit value.
|
||||||
|
if admin is not None:
|
||||||
|
user.admin = admin
|
||||||
# always set auth_state and commit,
|
# always set auth_state and commit,
|
||||||
# because there could be key-rotation or clearing of previous values
|
# because there could be key-rotation or clearing of previous values
|
||||||
# going on.
|
# going on.
|
||||||
|
@@ -138,6 +138,8 @@ class FormSpawner(MockSpawner):
|
|||||||
|
|
||||||
class MockPAMAuthenticator(PAMAuthenticator):
|
class MockPAMAuthenticator(PAMAuthenticator):
|
||||||
auth_state = None
|
auth_state = None
|
||||||
|
# If true, return admin users marked as admin.
|
||||||
|
return_admin = False
|
||||||
@default('admin_users')
|
@default('admin_users')
|
||||||
def _admin_users_default(self):
|
def _admin_users_default(self):
|
||||||
return {'admin'}
|
return {'admin'}
|
||||||
@@ -161,6 +163,11 @@ class MockPAMAuthenticator(PAMAuthenticator):
|
|||||||
'name': username,
|
'name': username,
|
||||||
'auth_state': self.auth_state,
|
'auth_state': self.auth_state,
|
||||||
}
|
}
|
||||||
|
elif self.return_admin:
|
||||||
|
return {
|
||||||
|
'name': username,
|
||||||
|
'admin': username in self.admin_users,
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
@@ -201,6 +201,49 @@ def test_auth_state(app, auth_state_enabled):
|
|||||||
assert auth_state == app.authenticator.auth_state
|
assert auth_state == app.authenticator.auth_state
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def use_auth_admin(app):
|
||||||
|
before_admin = app.authenticator.return_admin
|
||||||
|
app.authenticator.return_admin = True
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
app.authenticator.return_admin = before_admin
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_auth_admin_non_admin(app, use_auth_admin):
|
||||||
|
"""admin should be passed through for non-admin users"""
|
||||||
|
name = 'kiwi'
|
||||||
|
user = add_user(app.db, app, name=name, admin=False)
|
||||||
|
assert user.admin is False
|
||||||
|
cookies = yield app.login_user(name)
|
||||||
|
assert user.admin is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_auth_admin_is_admin(app, use_auth_admin):
|
||||||
|
"""admin should be passed through for admin users"""
|
||||||
|
# Admin user defined in MockPAMAuthenticator.
|
||||||
|
name = 'admin'
|
||||||
|
user = add_user(app.db, app, name=name, admin=False)
|
||||||
|
assert user.admin is False
|
||||||
|
cookies = yield app.login_user(name)
|
||||||
|
assert user.admin is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_auth_admin_retained_if_unset(app):
|
||||||
|
"""admin should be unchanged if authenticator doesn't return admin value"""
|
||||||
|
name = 'kiwi'
|
||||||
|
# Add user as admin.
|
||||||
|
user = add_user(app.db, app, name=name, admin=True)
|
||||||
|
assert user.admin is True
|
||||||
|
# User should remain unchanged.
|
||||||
|
cookies = yield app.login_user(name)
|
||||||
|
assert user.admin is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def auth_state_unavailable(auth_state_enabled):
|
def auth_state_unavailable(auth_state_enabled):
|
||||||
"""auth_state enabled at the Authenticator level,
|
"""auth_state enabled at the Authenticator level,
|
||||||
|
Reference in New Issue
Block a user