adding roles to services

This commit is contained in:
IvanaH8
2020-10-28 11:16:03 +01:00
parent 4142dc1bc0
commit 087c763d41
10 changed files with 158 additions and 36 deletions

View File

@@ -222,7 +222,12 @@ class APIHandler(BaseHandler):
def service_model(self, service):
"""Get the JSON model for a Service object"""
return {'kind': 'service', 'name': service.name, 'admin': service.admin}
return {
'kind': 'service',
'name': service.name,
'admin': service.admin,
'roles': [r.name for r in service.roles],
}
_user_model_types = {'name': str, 'admin': bool, 'groups': list, 'auth_state': dict}

View File

@@ -18,6 +18,7 @@ def service_model(service):
return {
'name': service.name,
'admin': service.admin,
'roles': [r.name for r in service.roles],
'url': service.url,
'prefix': service.server.base_url if service.server else '',
'command': service.command,

View File

@@ -1834,26 +1834,39 @@ class JupyterHub(Application):
role = orm.Role.find(db, predef_role['name'])
# handle users
if 'users' in predef_role.keys():
for username in predef_role['users']:
username = self.authenticator.normalize_username(username)
if not (
await maybe_future(self.authenticator.check_allowed(username, None))
await maybe_future(
self.authenticator.check_allowed(username, None)
)
):
raise ValueError(
"Username %r is not in Authenticator.allowed_users" % username
"Username %r is not in Authenticator.allowed_users"
% username
)
user = orm.User.find(db, name=username)
if user is None:
if not self.authenticator.validate_username(username):
raise ValueError("Role username %r is not valid" % username)
user = orm.User(name=username)
db.add(user)
raise ValueError("%r does not exist" % username)
else:
roles.add_user(db, user=user, role=role)
# make sure all users have at least one role (update with default)
for user in db.query(orm.User):
if len(user.roles) < 1:
roles.update_roles(db, user)
# handle services
if 'services' in predef_role.keys():
for servicename in predef_role['services']:
service = orm.Service.find(db, name=servicename)
if service is None:
raise ValueError("%r does not exist" % servicename)
else:
roles.add_user(db, user=service, role=role)
# make sure all users and services have at least one role (update with default)
Classes = [orm.User, orm.Service]
for ormClass in Classes:
for obj in db.query(ormClass):
if len(obj.roles) < 1:
roles.update_roles(db, obj)
db.commit()
@@ -1968,6 +1981,7 @@ class JupyterHub(Application):
base_url=self.base_url,
db=self.db,
orm=orm_service,
roles=orm_service.roles,
domain=domain,
host=host,
hub=self.hub,
@@ -2435,9 +2449,9 @@ class JupyterHub(Application):
self.init_oauth()
await self.init_users()
await self.init_groups()
await self.init_roles()
self.init_services()
await self.init_api_tokens()
await self.init_roles()
self.init_tornado_settings()
self.init_handlers()
self.init_tornado_application()

View File

@@ -141,6 +141,16 @@ user_role_map = Table(
Column('role_id', ForeignKey('roles.id', ondelete='CASCADE'), primary_key=True),
)
# service:role many:many mapping table
service_role_map = Table(
'service_role_map',
Base.metadata,
Column(
'service_id', ForeignKey('services.id', ondelete='CASCADE'), primary_key=True
),
Column('role_id', ForeignKey('roles.id', ondelete='CASCADE'), primary_key=True),
)
class Role(Base):
"""User Roles"""
@@ -151,6 +161,7 @@ class Role(Base):
description = Column(Unicode(1023))
scopes = Column(JSONList)
users = relationship('User', secondary='user_role_map', backref='roles')
services = relationship('Service', secondary='service_role_map', backref='roles')
def __repr__(self):
return "<%s %s (%s) - scopes: %s>" % (

View File

@@ -55,6 +55,8 @@ def add_role(db, role_dict):
else:
role.description = role_dict['description']
role.scopes = role_dict['scopes']
role.users = []
role.services = []
db.commit()

View File

@@ -267,6 +267,7 @@ class Service(LoggingConfigurable):
base_url = Unicode()
db = Any()
orm = Any()
roles = Any()
cookie_options = Dict()
oauth_provider = Any()

View File

@@ -10,8 +10,7 @@ from datetime import datetime
import jupyterhub
from jupyterhub import orm
# FIXME - for later versions of jupyterhub add code to test Roles
# from jupyterhub.orm import Role
# FIXME - for later versions of jupyterhub add code to test roles
def populate_db(url):

View File

@@ -1536,6 +1536,7 @@ async def test_get_services(app, mockservice_url):
mockservice.name: {
'name': mockservice.name,
'admin': True,
'roles': [],
'command': mockservice.command,
'pid': mockservice.proc.pid,
'prefix': mockservice.server.base_url,
@@ -1561,6 +1562,7 @@ async def test_get_service(app, mockservice_url):
assert service == {
'name': mockservice.name,
'admin': True,
'roles': [],
'command': mockservice.command,
'pid': mockservice.proc.pid,
'prefix': mockservice.server.base_url,

View File

@@ -4,33 +4,45 @@ from pytest import mark
from .. import orm
from .. import roles
from ..utils import maybe_future
from .mocking import MockHub
@mark.role
def test_roles(db):
def test_orm_roles(db):
"""Test orm roles setup"""
user = orm.User(name='falafel')
db.add(user)
service = orm.Service(name='kebab')
db.add(service)
role = orm.Role(name='default')
db.add(role)
db.commit()
assert role.users == []
assert role.services == []
assert user.roles == []
assert service.roles == []
role.users.append(user)
role.services.append(service)
db.commit()
assert role.users == [user]
assert user.roles == [role]
assert role.services == [service]
assert service.roles == [role]
db.delete(user)
db.commit()
assert role.users == []
db.delete(role)
db.commit()
assert service.roles == []
db.delete(service)
db.commit()
@mark.role
def test_role_delete_cascade(db):
def test_orm_role_delete_cascade(db):
"""Orm roles cascade"""
user1 = orm.User(name='user1')
user2 = orm.User(name='user2')
@@ -93,8 +105,25 @@ def test_role_delete_cascade(db):
@mark.role
async def test_load_roles(tmpdir, request):
"""Test loading default and predefined roles in app.py"""
async def test_load_default_roles(tmpdir, request):
"""Test loading default roles in app.py"""
kwargs = {}
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
await hub.init_roles()
# test default roles loaded to database
assert orm.Role.find(db, 'user') is not None
assert orm.Role.find(db, 'admin') is not None
assert orm.Role.find(db, 'server') is not None
@mark.role
async def test_load_roles_users(tmpdir, request):
"""Test loading predefined roles for users in app.py"""
roles_to_load = [
{
'name': 'teacher',
@@ -106,7 +135,7 @@ async def test_load_roles(tmpdir, request):
'name': 'user',
'description': 'Only read access',
'scopes': ['read:all'],
'users': ['test_user'],
'users': ['bilbo'],
},
]
kwargs = {'load_roles': roles_to_load}
@@ -116,20 +145,28 @@ async def test_load_roles(tmpdir, request):
hub = MockHub(**kwargs)
hub.init_db()
db = hub.db
hub.authenticator.admin_users = ['admin']
hub.authenticator.allowed_users = ['cyclops', 'gandalf', 'bilbo', 'gargamel']
await hub.init_users()
for user in db.query(orm.User):
print(user.name)
await hub.init_roles()
# test if the 'user' role has been overwritten
# test if the 'user' role has been overwritten and assigned
user_role = orm.Role.find(db, 'user')
admin_role = orm.Role.find(db, 'admin')
assert user_role is not None
assert user_role.scopes == ['read:all']
# test other default roles loaded to database
assert orm.Role.find(db, 'user') is not None
assert orm.Role.find(db, 'admin') is not None
assert orm.Role.find(db, 'server') is not None
# test if every existing user has a role (and no duplicates)
# test if every user has a role (and no duplicates)
# and admins have admin role
for user in db.query(orm.User):
assert len(user.roles) > 0
assert len(user.roles) == len(set(user.roles))
if user.admin:
assert admin_role in user.roles
assert user_role not in user.roles
# test if predefined roles loaded and assigned
teacher_role = orm.Role.find(db, name='teacher')
assert teacher_role is not None
@@ -137,3 +174,53 @@ async def test_load_roles(tmpdir, request):
assert teacher_role in gandalf_user.roles
cyclops_user = orm.User.find(db, name='cyclops')
assert teacher_role in cyclops_user.roles
@mark.role
async def test_load_roles_services(tmpdir, request):
roles_to_load = [
{
'name': 'culler',
'description': 'Cull idle servers',
'scopes': ['users:servers', 'admin:servers'],
'services': ['cull_idle'],
},
]
kwargs = {'load_roles': roles_to_load}
ssl_enabled = getattr(request.module, "ssl_enabled", False)
if ssl_enabled:
kwargs['internal_certs_location'] = str(tmpdir)
hub = MockHub(test_clean_db=False, **kwargs)
hub.init_db()
db = hub.db
# add test services to db
services = [
{'name': 'cull_idle', 'admin': False},
{'name': 'user_service', 'admin': False},
{'name': 'admin_service', 'admin': True},
]
for service_specs in services:
service = orm.Service.find(db, service_specs['name'])
if service is None:
service = orm.Service(
name=service_specs['name'], admin=service_specs['admin']
)
db.add(service)
db.commit()
await hub.init_roles()
# test if every service has a role (and no duplicates)
admin_role = orm.Role.find(db, name='admin')
user_role = orm.Role.find(db, name='user')
for service in db.query(orm.Service):
assert len(service.roles) > 0
assert len(service.roles) == len(set(service.roles))
if service.admin:
assert admin_role in service.roles
assert user_role not in service.roles
# test if predefined roles loaded and assigned
culler_role = orm.Role.find(db, name='culler')
cull_idle = orm.Service.find(db, name='cull_idle')
assert culler_role in cull_idle.roles
assert user_role not in cull_idle.roles

View File

@@ -295,7 +295,7 @@ async def test_hubauth_service_token(app, mockservice_url):
)
r.raise_for_status()
reply = r.json()
assert reply == {'kind': 'service', 'name': name, 'admin': False}
assert reply == {'kind': 'service', 'name': name, 'admin': False, 'roles': []}
assert not r.cookies
# token in ?token parameter
@@ -304,7 +304,7 @@ async def test_hubauth_service_token(app, mockservice_url):
)
r.raise_for_status()
reply = r.json()
assert reply == {'kind': 'service', 'name': name, 'admin': False}
assert reply == {'kind': 'service', 'name': name, 'admin': False, 'roles': []}
r = await async_requests.get(
public_url(app, mockservice_url) + '/whoami/?token=no-such-token',