From da443ebd4bcc34549b866ded436a525114a74e91 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Thu, 20 Jun 2019 21:54:51 +0300 Subject: [PATCH 1/6] Allow adding users to specific groups --- integration-tests/test_hub.py | 43 +++++++++++++++++++++++++++++++++++ tests/test_configurer.py | 18 +++++++++++++++ tljh/configurer.py | 10 ++++++++ tljh/jupyterhub_config.py | 8 +++++++ 4 files changed, 79 insertions(+) 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 From 88a09285d2def1669ed6f2de26ec45fde976c2e2 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Fri, 21 Jun 2019 11:38:26 +0300 Subject: [PATCH 2/6] Renamed group config option --- integration-tests/test_hub.py | 2 +- tests/test_configurer.py | 8 ++++---- tljh/configurer.py | 4 ++-- tljh/jupyterhub_config.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/integration-tests/test_hub.py b/integration-tests/test_hub.py index 48ad4d7..d47893d 100644 --- a/integration-tests/test_hub.py +++ b/integration-tests/test_hub.py @@ -156,7 +156,7 @@ async def test_user_group_adding(): 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, 'add-item', 'users.extra_user_groups.somegroup', username)).wait() assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait() try: diff --git a/tests/test_configurer.py b/tests/test_configurer.py index 3cc3c20..1857af5 100644 --- a/tests/test_configurer.py +++ b/tests/test_configurer.py @@ -129,22 +129,22 @@ def test_auth_dummy(): assert c.JupyterHub.authenticator_class == 'dummyauthenticator.DummyAuthenticator' assert c.DummyAuthenticator.password == 'test' - +from traitlets import Dict def test_user_groups(): """ Test setting user groups """ c = apply_mock_config({ 'users': { - 'groups': { + 'extra_user_groups': { "g1": ["u1", "u2"], - "g1": ["u3", "u4"] + "g2": ["u3", "u4"] }, } }) assert c.UserCreatingSpawner.user_groups == { "g1": ["u1", "u2"], - "g1": ["u3", "u4"] + "g2": ["u3", "u4"] } diff --git a/tljh/configurer.py b/tljh/configurer.py index b045f2b..eb5664b 100644 --- a/tljh/configurer.py +++ b/tljh/configurer.py @@ -27,7 +27,7 @@ default = { 'allowed': [], 'banned': [], 'admin': [], - 'groups': {} + 'extra_user_groups': {} }, 'limits': { 'memory': None, @@ -175,7 +175,7 @@ def update_usergroups(c, config): Set user groups """ users = config['users'] - c.UserCreatingSpawner.user_groups = users['groups'] + c.UserCreatingSpawner.user_groups = users['extra_user_groups'] def update_limits(c, config): diff --git a/tljh/jupyterhub_config.py b/tljh/jupyterhub_config.py index 11ee138..325d9c6 100644 --- a/tljh/jupyterhub_config.py +++ b/tljh/jupyterhub_config.py @@ -13,7 +13,7 @@ from tljh.normalize import generate_system_username from tljh.yaml import yaml from jupyterhub_traefik_proxy import TraefikTomlProxy -from traitlets import Any +from traitlets import Dict class UserCreatingSpawner(SystemdSpawner): """ @@ -21,7 +21,7 @@ class UserCreatingSpawner(SystemdSpawner): FIXME: Remove this somehow? """ - user_groups = Any(config=True) + user_groups = Dict(config=True) def start(self): """ From 58bc480b6c25427142459a3a2d0b67b5c1c6bf2a Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Fri, 21 Jun 2019 14:10:28 +0300 Subject: [PATCH 3/6] Extra user groups documentation --- docs/topic/tljh-config.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/topic/tljh-config.rst b/docs/topic/tljh-config.rst index bfc827b..5561fba 100644 --- a/docs/topic/tljh-config.rst +++ b/docs/topic/tljh-config.rst @@ -132,6 +132,34 @@ User Environment sudo tljh-config set user_environment.default_app jupyterlab +.. _tljh-set-extra-user-groups: + +Extra User Groups +================= + + +``users.extra_user_groups`` is a configuration option that can be used +to automatically add a user to a specific group. By default, there are +no extra groups defined. + +Users can be "paired" with the desired, **existing** groups using: + +* ``tljh-config set``, if only one user is to be added to the + desired group: + +.. code-block:: bash + + tljh-config set users.extra_user_groups.group1 user1 + +* ``tljh-config add-item``, if multiple users are to be added to + the group: + +.. code-block:: bash + + tljh-config add-item users.extra_user_groups.group1 user1 + tljh-config add-item users.extra_user_groups.group1 user2 + + .. _tljh-view-conf: View current configuration From 323847aa519d3d1ecc03e202d84500a78a02c6aa Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Mon, 1 Jul 2019 11:18:38 +0300 Subject: [PATCH 4/6] Validate user groups --- tljh/jupyterhub_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tljh/jupyterhub_config.py b/tljh/jupyterhub_config.py index 325d9c6..8ffecc7 100644 --- a/tljh/jupyterhub_config.py +++ b/tljh/jupyterhub_config.py @@ -21,7 +21,7 @@ class UserCreatingSpawner(SystemdSpawner): FIXME: Remove this somehow? """ - user_groups = Dict(config=True) + user_groups = Dict(key_trait=Unicode(), value_trait=List(Unicode(), config=True)) def start(self): """ From ba0454d23a012f1dc879e69c1c2a3acbbdadbc7a Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Mon, 1 Jul 2019 17:25:37 +0300 Subject: [PATCH 5/6] Fix import --- tljh/jupyterhub_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tljh/jupyterhub_config.py b/tljh/jupyterhub_config.py index 8ffecc7..801f817 100644 --- a/tljh/jupyterhub_config.py +++ b/tljh/jupyterhub_config.py @@ -13,7 +13,7 @@ from tljh.normalize import generate_system_username from tljh.yaml import yaml from jupyterhub_traefik_proxy import TraefikTomlProxy -from traitlets import Dict +from traitlets import Dict, Unicode, List class UserCreatingSpawner(SystemdSpawner): """ From 1842f46708b9826f71f66265e707f11b804452d5 Mon Sep 17 00:00:00 2001 From: GeorgianaElena Date: Mon, 1 Jul 2019 17:59:10 +0300 Subject: [PATCH 6/6] Fixed typo --- tljh/jupyterhub_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tljh/jupyterhub_config.py b/tljh/jupyterhub_config.py index 801f817..51455dc 100644 --- a/tljh/jupyterhub_config.py +++ b/tljh/jupyterhub_config.py @@ -21,7 +21,7 @@ class UserCreatingSpawner(SystemdSpawner): FIXME: Remove this somehow? """ - user_groups = Dict(key_trait=Unicode(), value_trait=List(Unicode(), config=True)) + user_groups = Dict(key_trait=Unicode(), value_trait=List(Unicode()), config=True) def start(self): """