Allow Authenticators to return an admin flag for users.

This commit is contained in:
Jesse Kinkead
2017-11-29 14:07:08 -08:00
parent bf911cf3a5
commit c1a7e0513b
4 changed files with 59 additions and 4 deletions

View File

@@ -235,6 +235,7 @@ class Authenticator(LoggingConfigurable):
'name': authenticated,
}
authenticated.setdefault('auth_state', None)
authenticated.setdefault('admin', None)
# normalize the username
authenticated['name'] = username = self.normalize_username(authenticated['name'])
@@ -269,10 +270,10 @@ class Authenticator(LoggingConfigurable):
Returns:
user (str or dict or None): The username of the authenticated user,
or None if Authentication failed.
If the Authenticator has state associated with the user,
it can return a dict with the keys 'name' and 'auth_state',
where 'name' is the username and 'auth_state' is a dictionary
of auth state that will be persisted.
The Authenticator may return a dict instead, which MUST have a
key 'name' holding the username, and may have two optional keys
set - 'auth_state', a dictionary of of auth state that will be
persisted; and 'admin', the admin setting value for the user.
"""
def pre_spawn_start(self, user, spawner):

View File

@@ -336,7 +336,11 @@ class BaseHandler(RequestHandler):
if authenticated:
username = authenticated['name']
auth_state = authenticated.get('auth_state')
admin = authenticated.get('admin')
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,
# because there could be key-rotation or clearing of previous values
# going on.

View File

@@ -138,6 +138,8 @@ class FormSpawner(MockSpawner):
class MockPAMAuthenticator(PAMAuthenticator):
auth_state = None
# If true, return admin users marked as admin.
return_admin = False
@default('admin_users')
def _admin_users_default(self):
return {'admin'}
@@ -161,6 +163,11 @@ class MockPAMAuthenticator(PAMAuthenticator):
'name': username,
'auth_state': self.auth_state,
}
elif self.return_admin:
return {
'name': username,
'admin': username in self.admin_users,
}
else:
return username

View File

@@ -201,6 +201,49 @@ def test_auth_state(app, auth_state_enabled):
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
def auth_state_unavailable(auth_state_enabled):
"""auth_state enabled at the Authenticator level,