mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-11 12:03:00 +00:00
fixed default roles for mocked services
This commit is contained in:
@@ -222,6 +222,7 @@ class APIHandler(BaseHandler):
|
|||||||
'kind': 'group',
|
'kind': 'group',
|
||||||
'name': group.name,
|
'name': group.name,
|
||||||
'users': [u.name for u in group.users],
|
'users': [u.name for u in group.users],
|
||||||
|
'roles': [r.name for r in group.roles],
|
||||||
}
|
}
|
||||||
|
|
||||||
def service_model(self, service):
|
def service_model(self, service):
|
||||||
@@ -233,9 +234,15 @@ class APIHandler(BaseHandler):
|
|||||||
'roles': [r.name for r in service.roles],
|
'roles': [r.name for r in service.roles],
|
||||||
}
|
}
|
||||||
|
|
||||||
_user_model_types = {'name': str, 'admin': bool, 'groups': list, 'auth_state': dict}
|
_user_model_types = {
|
||||||
|
'name': str,
|
||||||
|
'admin': bool,
|
||||||
|
'groups': list,
|
||||||
|
'roles': list,
|
||||||
|
'auth_state': dict,
|
||||||
|
}
|
||||||
|
|
||||||
_group_model_types = {'name': str, 'users': list}
|
_group_model_types = {'name': str, 'users': list, 'roles': list}
|
||||||
|
|
||||||
def _check_model(self, model, model_types, name):
|
def _check_model(self, model, model_types, name):
|
||||||
"""Check a model provided by a REST API request
|
"""Check a model provided by a REST API request
|
||||||
|
@@ -326,7 +326,8 @@ class JupyterHub(Application):
|
|||||||
'scopes': ['users', 'groups'],
|
'scopes': ['users', 'groups'],
|
||||||
'users': ['cyclops', 'gandalf'],
|
'users': ['cyclops', 'gandalf'],
|
||||||
'services': [],
|
'services': [],
|
||||||
'tokens': []
|
'tokens': [],
|
||||||
|
'groups': []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1827,7 +1828,7 @@ class JupyterHub(Application):
|
|||||||
async def init_roles(self):
|
async def init_roles(self):
|
||||||
"""Load default and predefined roles into the database"""
|
"""Load default and predefined roles into the database"""
|
||||||
db = self.db
|
db = self.db
|
||||||
role_bearers = ['users', 'services', 'tokens']
|
role_bearers = ['groups', 'users', 'services', 'tokens']
|
||||||
|
|
||||||
# load default roles
|
# load default roles
|
||||||
default_roles = roles.get_default_roles()
|
default_roles = roles.get_default_roles()
|
||||||
@@ -1857,11 +1858,18 @@ class JupyterHub(Application):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# make sure all users, services and tokens have at least one role (update with default)
|
# make sure all users, services and tokens have at least one role (update with default)
|
||||||
|
# groups can be without a role but all group members should have the same role(s) as the group
|
||||||
for bearer in role_bearers:
|
for bearer in role_bearers:
|
||||||
Class = roles.get_orm_class(bearer)
|
Class = roles.get_orm_class(bearer)
|
||||||
for obj in db.query(Class):
|
if bearer == 'groups':
|
||||||
if len(obj.roles) < 1:
|
for group in db.query(Class):
|
||||||
roles.update_roles(db, obj=obj, kind=bearer)
|
for user in group.users:
|
||||||
|
rolenames = roles.get_rolenames(group.roles)
|
||||||
|
roles.update_roles(db, obj=user, kind='users', roles=rolenames)
|
||||||
|
else:
|
||||||
|
for obj in db.query(Class):
|
||||||
|
if len(obj.roles) < 1:
|
||||||
|
roles.update_roles(db, obj=obj, kind=bearer)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
async def _add_tokens(self, token_dict, kind):
|
async def _add_tokens(self, token_dict, kind):
|
||||||
|
@@ -166,6 +166,14 @@ api_token_role_map = Table(
|
|||||||
Column('role_id', ForeignKey('roles.id', ondelete='CASCADE'), primary_key=True),
|
Column('role_id', ForeignKey('roles.id', ondelete='CASCADE'), primary_key=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# group:role many:many mapping table
|
||||||
|
group_role_map = Table(
|
||||||
|
'group_role_map',
|
||||||
|
Base.metadata,
|
||||||
|
Column('group_id', ForeignKey('groups.id', ondelete='CASCADE'), primary_key=True),
|
||||||
|
Column('role_id', ForeignKey('roles.id', ondelete='CASCADE'), primary_key=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Role(Base):
|
class Role(Base):
|
||||||
"""User Roles"""
|
"""User Roles"""
|
||||||
@@ -178,6 +186,7 @@ class Role(Base):
|
|||||||
users = relationship('User', secondary='user_role_map', backref='roles')
|
users = relationship('User', secondary='user_role_map', backref='roles')
|
||||||
services = relationship('Service', secondary='service_role_map', backref='roles')
|
services = relationship('Service', secondary='service_role_map', backref='roles')
|
||||||
tokens = relationship('APIToken', secondary='api_token_role_map', backref='roles')
|
tokens = relationship('APIToken', secondary='api_token_role_map', backref='roles')
|
||||||
|
groups = relationship('Group', secondary='group_role_map', backref='roles')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s %s (%s) - scopes: %s>" % (
|
return "<%s %s (%s) - scopes: %s>" % (
|
||||||
|
@@ -147,12 +147,26 @@ def get_orm_class(kind):
|
|||||||
Class = orm.Service
|
Class = orm.Service
|
||||||
elif kind == 'tokens':
|
elif kind == 'tokens':
|
||||||
Class = orm.APIToken
|
Class = orm.APIToken
|
||||||
|
elif kind == 'groups':
|
||||||
|
Class = orm.Group
|
||||||
else:
|
else:
|
||||||
raise ValueError("kind must be users, services or tokens, not %r" % kind)
|
raise ValueError(
|
||||||
|
"kind must be users, services, tokens or groups, not %r" % kind
|
||||||
|
)
|
||||||
|
|
||||||
return Class
|
return Class
|
||||||
|
|
||||||
|
|
||||||
|
def get_rolenames(role_list):
|
||||||
|
|
||||||
|
"""Return a list of rolenames from a list of roles"""
|
||||||
|
|
||||||
|
rolenames = []
|
||||||
|
for role in role_list:
|
||||||
|
rolenames.append(role.name)
|
||||||
|
return rolenames
|
||||||
|
|
||||||
|
|
||||||
def existing_only(func):
|
def existing_only(func):
|
||||||
|
|
||||||
"""Decorator for checking if objects and roles exist"""
|
"""Decorator for checking if objects and roles exist"""
|
||||||
@@ -176,7 +190,7 @@ def existing_only(func):
|
|||||||
@existing_only
|
@existing_only
|
||||||
def add_obj(db, objname, kind, rolename):
|
def add_obj(db, objname, kind, rolename):
|
||||||
|
|
||||||
"""Adds a role for users, services or tokens"""
|
"""Adds a role for users, services, tokens or groups"""
|
||||||
|
|
||||||
if rolename not in objname.roles:
|
if rolename not in objname.roles:
|
||||||
objname.roles.append(rolename)
|
objname.roles.append(rolename)
|
||||||
@@ -250,12 +264,28 @@ def update_roles(db, obj, kind, roles=None):
|
|||||||
else:
|
else:
|
||||||
add_obj(db, objname=obj.name, kind=kind, rolename=rolename)
|
add_obj(db, objname=obj.name, kind=kind, rolename=rolename)
|
||||||
else:
|
else:
|
||||||
|
# groups can be without a role
|
||||||
|
if Class == orm.Group:
|
||||||
|
pass
|
||||||
# tokens can have only 'user' role as default
|
# tokens can have only 'user' role as default
|
||||||
# assign the default only for user tokens
|
# assign the default only for user tokens
|
||||||
if Class == orm.APIToken:
|
elif Class == orm.APIToken:
|
||||||
if len(obj.roles) < 1 and obj.user is not None:
|
if len(obj.roles) < 1 and obj.user is not None:
|
||||||
user_role.tokens.append(obj)
|
user_role.tokens.append(obj)
|
||||||
db.commit()
|
db.commit()
|
||||||
# users and services can have 'user' or 'admin' roles as default
|
# users and services can have 'user' or 'admin' roles as default
|
||||||
else:
|
else:
|
||||||
switch_default_role(db, obj, kind, obj.admin)
|
switch_default_role(db, obj, kind, obj.admin)
|
||||||
|
|
||||||
|
|
||||||
|
def mock_roles(app, name, kind):
|
||||||
|
|
||||||
|
"""Loads and assigns default roles for mocked objects"""
|
||||||
|
|
||||||
|
Class = get_orm_class(kind)
|
||||||
|
|
||||||
|
obj = Class.find(app.db, name=name)
|
||||||
|
default_roles = get_default_roles()
|
||||||
|
for role in default_roles:
|
||||||
|
add_role(app.db, role)
|
||||||
|
update_roles(db=app.db, obj=obj, kind=kind)
|
||||||
|
@@ -44,6 +44,7 @@ import jupyterhub.services.service
|
|||||||
from . import mocking
|
from . import mocking
|
||||||
from .. import crypto
|
from .. import crypto
|
||||||
from .. import orm
|
from .. import orm
|
||||||
|
from ..roles import mock_roles
|
||||||
from ..utils import random_port
|
from ..utils import random_port
|
||||||
from .mocking import MockHub
|
from .mocking import MockHub
|
||||||
from .test_services import mockservice_cmd
|
from .test_services import mockservice_cmd
|
||||||
@@ -245,6 +246,7 @@ def _mockservice(request, app, url=False):
|
|||||||
):
|
):
|
||||||
app.services = [spec]
|
app.services = [spec]
|
||||||
app.init_services()
|
app.init_services()
|
||||||
|
mock_roles(app, name, 'services')
|
||||||
assert name in app._service_map
|
assert name in app._service_map
|
||||||
service = app._service_map[name]
|
service = app._service_map[name]
|
||||||
|
|
||||||
|
@@ -1446,7 +1446,7 @@ async def test_groups_list(app):
|
|||||||
r = await api_request(app, 'groups')
|
r = await api_request(app, 'groups')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply == [{'kind': 'group', 'name': 'alphaflight', 'users': []}]
|
assert reply == [{'kind': 'group', 'name': 'alphaflight', 'users': [], 'roles': []}]
|
||||||
|
|
||||||
|
|
||||||
@mark.group
|
@mark.group
|
||||||
@@ -1481,7 +1481,12 @@ async def test_group_get(app):
|
|||||||
r = await api_request(app, 'groups/alphaflight')
|
r = await api_request(app, 'groups/alphaflight')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply == {'kind': 'group', 'name': 'alphaflight', 'users': ['sasquatch']}
|
assert reply == {
|
||||||
|
'kind': 'group',
|
||||||
|
'name': 'alphaflight',
|
||||||
|
'users': ['sasquatch'],
|
||||||
|
'roles': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@mark.group
|
@mark.group
|
||||||
@@ -1594,7 +1599,7 @@ async def test_get_services(app, mockservice_url):
|
|||||||
mockservice.name: {
|
mockservice.name: {
|
||||||
'name': mockservice.name,
|
'name': mockservice.name,
|
||||||
'admin': True,
|
'admin': True,
|
||||||
'roles': [],
|
'roles': ['admin'],
|
||||||
'command': mockservice.command,
|
'command': mockservice.command,
|
||||||
'pid': mockservice.proc.pid,
|
'pid': mockservice.proc.pid,
|
||||||
'prefix': mockservice.server.base_url,
|
'prefix': mockservice.server.base_url,
|
||||||
@@ -1620,7 +1625,7 @@ async def test_get_service(app, mockservice_url):
|
|||||||
assert service == {
|
assert service == {
|
||||||
'name': mockservice.name,
|
'name': mockservice.name,
|
||||||
'admin': True,
|
'admin': True,
|
||||||
'roles': [],
|
'roles': ['admin'],
|
||||||
'command': mockservice.command,
|
'command': mockservice.command,
|
||||||
'pid': mockservice.proc.pid,
|
'pid': mockservice.proc.pid,
|
||||||
'prefix': mockservice.server.base_url,
|
'prefix': mockservice.server.base_url,
|
||||||
|
@@ -26,6 +26,18 @@ def test_orm_roles(db):
|
|||||||
db.add(service_role)
|
db.add(service_role)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
group_role = orm.Role(name='group', scopes=['read:users'])
|
||||||
|
db.add(group_role)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
group_role = orm.Role(name='group', scopes=['read:users'])
|
||||||
|
db.add(group_role)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
group_role = orm.Role(name='group', scopes=['read:users'])
|
||||||
|
db.add(group_role)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
user = orm.User(name='falafel')
|
user = orm.User(name='falafel')
|
||||||
db.add(user)
|
db.add(user)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -34,20 +46,30 @@ def test_orm_roles(db):
|
|||||||
db.add(service)
|
db.add(service)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
group = orm.Group(name='fast-food')
|
||||||
|
db.add(group)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
assert user_role.users == []
|
assert user_role.users == []
|
||||||
assert user_role.services == []
|
assert user_role.services == []
|
||||||
|
assert user_role.groups == []
|
||||||
assert service_role.users == []
|
assert service_role.users == []
|
||||||
assert service_role.services == []
|
assert service_role.services == []
|
||||||
|
assert service_role.groups == []
|
||||||
assert user.roles == []
|
assert user.roles == []
|
||||||
assert service.roles == []
|
assert service.roles == []
|
||||||
|
assert group.roles == []
|
||||||
|
|
||||||
user_role.users.append(user)
|
user_role.users.append(user)
|
||||||
service_role.services.append(service)
|
service_role.services.append(service)
|
||||||
|
group_role.groups.append(group)
|
||||||
db.commit()
|
db.commit()
|
||||||
assert user_role.users == [user]
|
assert user_role.users == [user]
|
||||||
assert user.roles == [user_role]
|
assert user.roles == [user_role]
|
||||||
assert service_role.services == [service]
|
assert service_role.services == [service]
|
||||||
assert service.roles == [service_role]
|
assert service.roles == [service_role]
|
||||||
|
assert group_role.groups == [group]
|
||||||
|
assert group.roles == [group_role]
|
||||||
|
|
||||||
# check token creation without specifying its role
|
# check token creation without specifying its role
|
||||||
# assigns it the default 'user' role
|
# assigns it the default 'user' role
|
||||||
@@ -67,16 +89,22 @@ def test_orm_roles(db):
|
|||||||
db.commit()
|
db.commit()
|
||||||
assert user_role.users == []
|
assert user_role.users == []
|
||||||
assert user_token not in user_role.tokens
|
assert user_token not in user_role.tokens
|
||||||
# check deleting the service token removes it from 'service' role
|
# check deleting the service token removes it from service_role
|
||||||
db.delete(service_token)
|
db.delete(service_token)
|
||||||
db.commit()
|
db.commit()
|
||||||
assert service_token not in service_role.tokens
|
assert service_token not in service_role.tokens
|
||||||
# check deleting the 'service' role removes it from service roles
|
# check deleting the service_role removes it from service.roles
|
||||||
db.delete(service_role)
|
db.delete(service_role)
|
||||||
db.commit()
|
db.commit()
|
||||||
assert service.roles == []
|
assert service.roles == []
|
||||||
|
# check deleting the group removes it from group_roles
|
||||||
|
db.delete(group)
|
||||||
|
db.commit()
|
||||||
|
assert group_role.groups == []
|
||||||
|
|
||||||
|
# clean up db
|
||||||
db.delete(service)
|
db.delete(service)
|
||||||
|
db.delete(group_role)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
@@ -310,6 +338,76 @@ async def test_load_roles_services(tmpdir, request):
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@mark.role
|
||||||
|
async def test_load_roles_groups(tmpdir, request):
|
||||||
|
"""Test loading predefined roles for groups in app.py"""
|
||||||
|
groups_to_load = {
|
||||||
|
'group1': ['gandalf'],
|
||||||
|
'group2': ['bilbo', 'gargamel'],
|
||||||
|
'group3': ['cyclops'],
|
||||||
|
}
|
||||||
|
roles_to_load = [
|
||||||
|
{
|
||||||
|
'name': 'assistant',
|
||||||
|
'description': 'Access users information only',
|
||||||
|
'scopes': ['read:users'],
|
||||||
|
'groups': ['group2'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'head',
|
||||||
|
'description': 'Whole user access',
|
||||||
|
'scopes': ['users', 'admin:users'],
|
||||||
|
'groups': ['group3'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
kwargs = {'load_groups': groups_to_load, 'load_roles': roles_to_load}
|
||||||
|
ssl_enabled = getattr(request.module, "ssl_enabled", False)
|
||||||
|
if ssl_enabled:
|
||||||
|
kwargs['internal_certs_location'] = str(tmpdir)
|
||||||
|
hub = MockHub(**kwargs)
|
||||||
|
hub.init_db()
|
||||||
|
db = hub.db
|
||||||
|
hub.authenticator.allowed_users = ['cyclops', 'gandalf', 'bilbo', 'gargamel']
|
||||||
|
await hub.init_users()
|
||||||
|
await hub.init_groups()
|
||||||
|
await hub.init_roles()
|
||||||
|
|
||||||
|
assist_role = orm.Role.find(db, name='assistant')
|
||||||
|
head_role = orm.Role.find(db, name='head')
|
||||||
|
user_role = orm.Role.find(db, name='user')
|
||||||
|
|
||||||
|
group1 = orm.Group.find(db, name='group1')
|
||||||
|
group2 = orm.Group.find(db, name='group2')
|
||||||
|
group3 = orm.Group.find(db, name='group3')
|
||||||
|
|
||||||
|
gandalf = orm.User.find(db, name='gandalf')
|
||||||
|
bilbo = orm.User.find(db, name='bilbo')
|
||||||
|
gargamel = orm.User.find(db, name='gargamel')
|
||||||
|
cyclops = orm.User.find(db, name='cyclops')
|
||||||
|
|
||||||
|
# test group roles
|
||||||
|
assert group1.roles == []
|
||||||
|
assert group2 in assist_role.groups
|
||||||
|
assert group3 in head_role.groups
|
||||||
|
|
||||||
|
# test group members' roles
|
||||||
|
assert assist_role in bilbo.roles
|
||||||
|
assert assist_role in gargamel.roles
|
||||||
|
assert head_role in cyclops.roles
|
||||||
|
|
||||||
|
# check the default user_role assignment
|
||||||
|
# FIXME - should users with group roles still have default?
|
||||||
|
assert user_role in gandalf.roles
|
||||||
|
assert user_role not in bilbo.roles
|
||||||
|
assert user_role not in gargamel.roles
|
||||||
|
assert user_role not in cyclops.roles
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME
|
||||||
|
# @mark.role
|
||||||
|
# async def test_group_roles_delete_cascade(tmpdir, request):
|
||||||
|
|
||||||
|
|
||||||
@mark.role
|
@mark.role
|
||||||
async def test_load_roles_tokens(tmpdir, request):
|
async def test_load_roles_tokens(tmpdir, request):
|
||||||
services = [
|
services = [
|
||||||
|
Reference in New Issue
Block a user