From 6cfa789d6a394af9998f8e39e041efe24eeeb2a0 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 25 Sep 2023 12:30:55 +0200 Subject: [PATCH] Backport PR #4570: fix mutation of frozenset in scope intersection --- jupyterhub/scopes.py | 24 +++++++++++++++--------- jupyterhub/tests/test_scopes.py | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/jupyterhub/scopes.py b/jupyterhub/scopes.py index 87428abe..3031432f 100644 --- a/jupyterhub/scopes.py +++ b/jupyterhub/scopes.py @@ -254,8 +254,12 @@ def _intersect_expanded_scopes(scopes_a, scopes_b, db=None): } # resolve hierarchies (group/user/server) in both directions - common_servers = common_filters[base].get("server", set()) - common_users = common_filters[base].get("user", set()) + common_servers = initial_common_servers = common_filters[base].get( + "server", frozenset() + ) + common_users = initial_common_users = common_filters[base].get( + "user", frozenset() + ) for a, b in [(filters_a, filters_b), (filters_b, filters_a)]: if 'server' in a and b.get('server') != a['server']: @@ -267,7 +271,7 @@ def _intersect_expanded_scopes(scopes_a, scopes_b, db=None): for server in servers: username, _, servername = server.partition("/") if username in b['user']: - common_servers.add(server) + common_servers = common_servers | {server} # resolve group/server hierarchy if db available servers = servers.difference(common_servers) @@ -276,7 +280,7 @@ def _intersect_expanded_scopes(scopes_a, scopes_b, db=None): for server in servers: server_groups = groups_for_server(server) if server_groups & b['group']: - common_servers.add(server) + common_servers = common_servers | {server} # resolve group/user hierarchy if db available and user sets aren't identical if ( @@ -290,14 +294,16 @@ def _intersect_expanded_scopes(scopes_a, scopes_b, db=None): for username in users: groups = groups_for_user(username) if groups & b["group"]: - common_users.add(username) + common_users = common_users | {username} - # add server filter if there wasn't one before - if common_servers and "server" not in common_filters[base]: + # add server filter if it's non-empty + # and it changed + if common_servers and common_servers != initial_common_servers: common_filters[base]["server"] = common_servers - # add user filter if it's non-empty and there wasn't one before - if common_users and "user" not in common_filters[base]: + # add user filter if it's non-empty + # and it changed + if common_users and common_users != initial_common_users: common_filters[base]["user"] = common_users intersection = unparse_scopes(common_filters) diff --git a/jupyterhub/tests/test_scopes.py b/jupyterhub/tests/test_scopes.py index 0a6dae56..c72f2768 100644 --- a/jupyterhub/tests/test_scopes.py +++ b/jupyterhub/tests/test_scopes.py @@ -913,6 +913,22 @@ def test_intersect_expanded_scopes(left, right, expected, should_warn, recwarn): ["read:users!user=uy"], {"gx": ["ux"], "gy": ["uy"]}, ), + ( + # make sure the group > user > server hierarchy + # is managed + ["read:servers!server=ux/server", "read:servers!group=gy"], + ["read:servers!server=uy/server", "read:servers!user=ux"], + ["read:servers!server=ux/server", "read:servers!server=uy/server"], + {"gx": ["ux"], "gy": ["uy"]}, + ), + ( + # make sure the group > user hierarchy + # is managed + ["read:servers!user=ux", "read:servers!group=gy"], + ["read:servers!user=uy", "read:servers!group=gx"], + ["read:servers!user=ux", "read:servers!user=uy"], + {"gx": ["ux"], "gy": ["uy"]}, + ), ], ) def test_intersect_groups(request, db, left, right, expected, groups):