diff --git a/jupyterhub/apihandlers/base.py b/jupyterhub/apihandlers/base.py index 6e96bea8..e8ebc82a 100644 --- a/jupyterhub/apihandlers/base.py +++ b/jupyterhub/apihandlers/base.py @@ -305,6 +305,7 @@ class APIHandler(BaseHandler): if req_scope in self.parsed_scopes: scope_filter = self.get_scope_filter(req_scope) if scope_filter(group, kind='group'): + model['roles'] = [r.name for r in group.roles] model['users'] = [u.name for u in group.users] return model diff --git a/jupyterhub/roles.py b/jupyterhub/roles.py index 1b814987..81ba6425 100644 --- a/jupyterhub/roles.py +++ b/jupyterhub/roles.py @@ -86,6 +86,7 @@ def _get_scope_hierarchy(): """ scopes = { + 'self': None, 'all': None, # Optional 'read:all' as subscope, not implemented at this stage 'users': ['read:users', 'users:activity', 'users:servers'], 'read:users': [ @@ -256,7 +257,7 @@ def create_role(db, role_dict): db.commit() -def remove_role(db, rolename): +def delete_role(db, rolename): """Removes a role from database""" # default roles are not removable @@ -424,6 +425,44 @@ def update_roles(db, entity, roles): grant_role(db, entity=entity, rolename=rolename) +def add_predef_roles_tokens(db, predef_roles): + + """Adds tokens to predefined roles in config file + if their permissions allow""" + + for predef_role in predef_roles: + if 'tokens' in predef_role.keys(): + token_role = orm.Role.find(db, name=predef_role['name']) + for token_name in predef_role['tokens']: + token = orm.APIToken.find(db, token_name) + if token is None: + raise ValueError( + "Token %r does not exist and cannot assign it to role %r" + % (token_name, token_role.name) + ) + else: + update_roles(db, token, roles=[token_role.name]) + + +def check_for_default_roles(db, bearer): + + """Checks that role bearers have at least one role (default if none). + Groups can be without a role""" + + Class = orm.get_class(bearer) + if Class == orm.Group: + pass + else: + for obj in ( + db.query(Class) + .outerjoin(orm.Role, Class.roles) + .group_by(Class.id) + .having(func.count(orm.Role.id) == 0) + ): + assign_default_roles(db, obj) + db.commit() + + def mock_roles(app, name, kind): """Loads and assigns default roles for mocked objects""" Class = orm.get_class(kind) diff --git a/jupyterhub/tests/test_roles.py b/jupyterhub/tests/test_roles.py index 2635834d..5192ccf9 100644 --- a/jupyterhub/tests/test_roles.py +++ b/jupyterhub/tests/test_roles.py @@ -10,6 +10,7 @@ from tornado.log import app_log from .. import orm from .. import roles +from ..scopes import get_scopes_for from ..utils import maybe_future from .mocking import MockHub from .utils import add_user @@ -326,13 +327,13 @@ async def test_delete_roles(db, role_type, rolename, response_type, response): assert check_role is not None # check the role is deleted and info raised with pytest.warns(response): - roles.remove_role(db, rolename) + roles.delete_role(db, rolename) check_role = orm.Role.find(db, rolename) assert check_role is None elif response_type == 'error': with pytest.raises(response): - roles.remove_role(db, rolename) + roles.delete_role(db, rolename) @mark.role @@ -367,20 +368,20 @@ async def test_scope_existence(tmpdir, request, role, response): db = hub.db if response == 'existing': - roles.add_role(db, role) + roles.create_role(db, role) added_role = orm.Role.find(db, role['name']) assert added_role is not None assert added_role.scopes == role['scopes'] elif response == NameError: with pytest.raises(response): - roles.add_role(db, role) + roles.create_role(db, role) added_role = orm.Role.find(db, role['name']) assert added_role is None # delete the tested roles if added_role: - roles.remove_role(db, added_role.name) + roles.delete_role(db, added_role.name) @mark.role @@ -427,7 +428,7 @@ async def test_load_roles_users(tmpdir, request): # delete the test roles for role in roles_to_load: - roles.remove_role(db, role['name']) + roles.delete_role(db, role['name']) @mark.role @@ -507,7 +508,7 @@ async def test_load_roles_services(tmpdir, request): # delete the test roles for role in roles_to_load: - roles.remove_role(db, role['name']) + roles.delete_role(db, role['name']) @mark.role @@ -556,7 +557,7 @@ async def test_load_roles_groups(tmpdir, request): # delete the test roles for role in roles_to_load: - roles.remove_role(db, role['name']) + roles.delete_role(db, role['name']) @mark.role @@ -609,7 +610,7 @@ async def test_load_roles_user_tokens(tmpdir, request): # delete the test roles for role in roles_to_load: - roles.remove_role(db, role['name']) + roles.delete_role(db, role['name']) @mark.role @@ -652,12 +653,14 @@ async def test_load_roles_user_tokens_not_allowed(tmpdir, request): # delete the test roles for role in roles_to_load: - roles.remove_role(db, role['name']) + roles.delete_role(db, role['name']) @mark.role async def test_load_roles_service_tokens(tmpdir, request): - services = [{'name': 'idle-culler', 'api_token': 'another-secret-token'}] + services = [ + {'name': 'idle-culler', 'api_token': 'another-secret-token'}, + ] service_tokens = { 'another-secret-token': 'idle-culler', } @@ -674,12 +677,6 @@ async def test_load_roles_service_tokens(tmpdir, request): 'services': ['idle-culler'], 'tokens': ['another-secret-token'], }, - { - 'name': 'admin', - 'description': 'Admin access', - 'scopes': ['a lot'], - 'users': ['admin'], - }, ] kwargs = { 'load_roles': roles_to_load, @@ -714,7 +711,7 @@ async def test_load_roles_service_tokens(tmpdir, request): # delete the test roles for role in roles_to_load: - roles.remove_role(db, role['name']) + roles.delete_role(db, role['name']) @mark.role @@ -770,7 +767,7 @@ async def test_load_roles_service_tokens_not_allowed(tmpdir, request): # delete the test roles for role in roles_to_load: - roles.remove_role(db, role['name']) + roles.delete_role(db, role['name']) @mark.role @@ -858,7 +855,7 @@ async def test_self_expansion(app, kind, has_user_scopes): # test expansion of token scopes orm_obj.new_api_token() print(orm_obj.api_tokens[0]) - token_scopes = scopes.get_scopes_for(orm_obj.api_tokens[0]) + token_scopes = get_scopes_for(orm_obj.api_tokens[0]) print(token_scopes) assert bool(token_scopes) == has_user_scopes app.db.delete(orm_obj) diff --git a/jupyterhub/tests/test_scopes.py b/jupyterhub/tests/test_scopes.py index 0706e8f4..aed175e9 100644 --- a/jupyterhub/tests/test_scopes.py +++ b/jupyterhub/tests/test_scopes.py @@ -575,11 +575,11 @@ async def test_metascope_all_expansion(app): ( [ 'read:users:name!user=almond', - 'read:users:servers!server=almond/bianca', # fixme: server-scope not working yet + 'read:users:servers!server=almond/bianca', 'admin:users:server_state!server=almond/bianca', ], False, - 1, + 0, # fixme: server-scope not working yet {'name', 'state'}, set(), ),