diff --git a/integration-tests/test_hub.py b/integration-tests/test_hub.py index 550e1ab..48ad4d7 100644 --- a/integration-tests/test_hub.py +++ b/integration-tests/test_hub.py @@ -10,6 +10,7 @@ import pwd import grp import sys import subprocess +from os import system from tljh.normalize import generate_system_username @@ -141,6 +142,48 @@ async def test_long_username(): raise +@pytest.mark.asyncio +async def test_user_group_adding(): + """ + User logs in, and we check if they are added to the specified group. + """ + # This *must* be localhost, not an IP + # aiohttp throws away cookies if we are connecting to an IP! + hub_url = 'http://localhost' + username = secrets.token_hex(8) + groups = {"somegroup": [username]} + # Create the group we want to add the user to + system('groupadd somegroup') + + assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'set', 'auth.type', 'dummyauthenticator.DummyAuthenticator')).wait() + assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'add-item', 'users.groups.somegroup', username)).wait() + assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait() + + try: + async with User(username, hub_url, partial(login_dummy, password='')) as u: + await u.login() + await u.ensure_server() + + # Assert that the user exists + system_username = generate_system_username(f'jupyter-{username}') + assert pwd.getpwnam(system_username) is not None + + # Assert that the user was added to the specified group + assert f'jupyter-{username}' in grp.getgrnam('somegroup').gr_mem + + await u.stop_server() + # Delete the group + system('groupdel somegroup') + except: + # If we have any errors, print jupyterhub logs before exiting + subprocess.check_call([ + 'journalctl', + '-u', 'jupyterhub', + '--no-pager' + ]) + raise + + @pytest.mark.asyncio async def test_idle_server_culled(): """ diff --git a/tests/test_configurer.py b/tests/test_configurer.py index a2ef1e2..3cc3c20 100644 --- a/tests/test_configurer.py +++ b/tests/test_configurer.py @@ -130,6 +130,24 @@ def test_auth_dummy(): assert c.DummyAuthenticator.password == 'test' +def test_user_groups(): + """ + Test setting user groups + """ + c = apply_mock_config({ + 'users': { + 'groups': { + "g1": ["u1", "u2"], + "g1": ["u3", "u4"] + }, + } + }) + assert c.UserCreatingSpawner.user_groups == { + "g1": ["u1", "u2"], + "g1": ["u3", "u4"] + } + + def test_auth_firstuse(): """ Test setting FirstUse Authenticator options diff --git a/tljh/configurer.py b/tljh/configurer.py index 2e549db..b045f2b 100644 --- a/tljh/configurer.py +++ b/tljh/configurer.py @@ -27,6 +27,7 @@ default = { 'allowed': [], 'banned': [], 'admin': [], + 'groups': {} }, 'limits': { 'memory': None, @@ -93,6 +94,7 @@ def apply_config(config_overrides, c): update_auth(c, tljh_config) update_userlists(c, tljh_config) + update_usergroups(c, tljh_config) update_limits(c, tljh_config) update_user_environment(c, tljh_config) update_user_account_config(c, tljh_config) @@ -168,6 +170,14 @@ def update_userlists(c, config): c.Authenticator.admin_users = set(users['admin']) +def update_usergroups(c, config): + """ + Set user groups + """ + users = config['users'] + c.UserCreatingSpawner.user_groups = users['groups'] + + def update_limits(c, config): """ Set user server limits diff --git a/tljh/jupyterhub_config.py b/tljh/jupyterhub_config.py index 7f11bfa..11ee138 100644 --- a/tljh/jupyterhub_config.py +++ b/tljh/jupyterhub_config.py @@ -13,12 +13,16 @@ from tljh.normalize import generate_system_username from tljh.yaml import yaml from jupyterhub_traefik_proxy import TraefikTomlProxy +from traitlets import Any + class UserCreatingSpawner(SystemdSpawner): """ SystemdSpawner with user creation on spawn. FIXME: Remove this somehow? """ + user_groups = Any(config=True) + def start(self): """ Perform system user activities before starting server @@ -34,6 +38,10 @@ class UserCreatingSpawner(SystemdSpawner): user.ensure_user_group(system_username, 'jupyterhub-admins') else: user.remove_user_group(system_username, 'jupyterhub-admins') + if self.user_groups: + for group, users in self.user_groups.items(): + if self.user.name in users: + user.ensure_user_group(system_username, group) return super().start() c.JupyterHub.spawner_class = UserCreatingSpawner