mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-16 06:22:59 +00:00
Unified service model
This commit is contained in:
@@ -298,10 +298,17 @@ class APIHandler(BaseHandler):
|
|||||||
'name': service.name,
|
'name': service.name,
|
||||||
'roles': [r.name for r in service.roles],
|
'roles': [r.name for r in service.roles],
|
||||||
'admin': service.admin,
|
'admin': service.admin,
|
||||||
|
'url': getattr(service, 'url', ''),
|
||||||
|
'prefix': service.server.base_url if getattr(service, 'server', '') else '',
|
||||||
|
'command': getattr(service, 'command', ''),
|
||||||
|
'pid': service.proc.pid if getattr(service, 'proc', '') else 0,
|
||||||
|
'info': getattr(service, 'info', ''),
|
||||||
|
'display': getattr(service, 'display', ''),
|
||||||
}
|
}
|
||||||
# todo: remove admin key now we have roles?
|
# todo: remove admin key now we have roles?
|
||||||
|
# todo: More default keys present?
|
||||||
access_map = {
|
access_map = {
|
||||||
'read:services': {'kind', 'name', 'roles', 'admin'},
|
'read:services': set(model.keys()),
|
||||||
'read:services:name': {'kind', 'name'},
|
'read:services:name': {'kind', 'name'},
|
||||||
'read:services:roles': {'kind', 'name', 'roles'},
|
'read:services:roles': {'kind', 'name', 'roles'},
|
||||||
}
|
}
|
||||||
|
@@ -6,34 +6,16 @@ Currently GET-only, no actions can be taken to modify services.
|
|||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from tornado import web
|
|
||||||
|
|
||||||
from .. import orm
|
|
||||||
from ..scopes import needs_scope
|
from ..scopes import needs_scope
|
||||||
from .base import APIHandler
|
from .base import APIHandler
|
||||||
|
|
||||||
|
|
||||||
def service_model(service):
|
|
||||||
"""Produce the model for a 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,
|
|
||||||
'pid': service.proc.pid if service.proc else 0,
|
|
||||||
'info': service.info,
|
|
||||||
'display': service.display,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceListAPIHandler(APIHandler):
|
class ServiceListAPIHandler(APIHandler):
|
||||||
@needs_scope('read:services')
|
@needs_scope('read:services', 'read:services:name', 'read:services:roles')
|
||||||
def get(self):
|
def get(self):
|
||||||
scope_filter = self.get_scope_filter('read:services')
|
scope_filter = self.get_scope_filter('read:services')
|
||||||
data = {
|
data = {
|
||||||
name: service_model(service)
|
name: self.service_model(service)
|
||||||
for name, service in self.services.items()
|
for name, service in self.services.items()
|
||||||
if scope_filter(service, kind='service')
|
if scope_filter(service, kind='service')
|
||||||
}
|
}
|
||||||
@@ -41,10 +23,10 @@ class ServiceListAPIHandler(APIHandler):
|
|||||||
|
|
||||||
|
|
||||||
class ServiceAPIHandler(APIHandler):
|
class ServiceAPIHandler(APIHandler):
|
||||||
@needs_scope('read:services')
|
@needs_scope('read:services', 'read:services:name', 'read:services:roles')
|
||||||
def get(self, service_name):
|
def get(self, service_name):
|
||||||
service = self.services[service_name]
|
service = self.services[service_name]
|
||||||
self.write(json.dumps(service_model(service)))
|
self.write(json.dumps(self.service_model(service)))
|
||||||
|
|
||||||
|
|
||||||
default_handlers = [
|
default_handlers = [
|
||||||
|
@@ -1606,6 +1606,7 @@ async def test_get_services(app, mockservice_url):
|
|||||||
services = r.json()
|
services = r.json()
|
||||||
assert services == {
|
assert services == {
|
||||||
mockservice.name: {
|
mockservice.name: {
|
||||||
|
'kind': 'service',
|
||||||
'name': mockservice.name,
|
'name': mockservice.name,
|
||||||
'admin': True,
|
'admin': True,
|
||||||
'roles': ['admin'],
|
'roles': ['admin'],
|
||||||
@@ -1631,6 +1632,7 @@ async def test_get_service(app, mockservice_url):
|
|||||||
|
|
||||||
service = r.json()
|
service = r.json()
|
||||||
assert service == {
|
assert service == {
|
||||||
|
'kind': 'service',
|
||||||
'name': mockservice.name,
|
'name': mockservice.name,
|
||||||
'admin': True,
|
'admin': True,
|
||||||
'roles': ['admin'],
|
'roles': ['admin'],
|
||||||
|
@@ -763,13 +763,39 @@ async def test_resolve_token_permissions(
|
|||||||
assert token_retained_scopes == intersection_scopes
|
assert token_retained_scopes == intersection_scopes
|
||||||
|
|
||||||
|
|
||||||
|
@mark.parametrize(
|
||||||
|
"scopes, model_keys",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{'read:services'},
|
||||||
|
{
|
||||||
|
'command',
|
||||||
|
'name',
|
||||||
|
'kind',
|
||||||
|
'info',
|
||||||
|
'display',
|
||||||
|
'pid',
|
||||||
|
'admin',
|
||||||
|
'prefix',
|
||||||
|
'url',
|
||||||
|
'roles',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
({'read:services:roles', 'read:users:names'}, {'name', 'kind', 'roles'}),
|
||||||
|
({'read:services:name'}, {'name', 'kind'}),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_service_model_filtering(
|
||||||
|
app, scopes, model_keys, create_user_with_scopes, create_service_with_scopes
|
||||||
|
):
|
||||||
|
user = create_user_with_scopes(*scopes, 'teddy')
|
||||||
|
service = create_service_with_scopes()
|
||||||
|
r = await api_request(
|
||||||
|
app, 'services', service.name, headers=auth_header(app.db, user.name)
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert model_keys == r.json().keys()
|
||||||
|
|
||||||
|
|
||||||
async def test_roles_access(app, create_user_with_scopes):
|
async def test_roles_access(app, create_user_with_scopes):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def test_service_model_filtering(app, create_user_with_scopes):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
async def test_group_model_filtering(app, create_user_with_scopes):
|
|
||||||
pass
|
|
||||||
|
@@ -298,7 +298,8 @@ async def test_hubauth_service_token(app, mockservice_url):
|
|||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply == {'kind': 'service', 'name': name, 'admin': False, 'roles': []}
|
service_model = {'kind': 'service', 'name': name, 'admin': False, 'roles': []}
|
||||||
|
assert service_model.items() <= reply.items()
|
||||||
assert not r.cookies
|
assert not r.cookies
|
||||||
|
|
||||||
# token in ?token parameter
|
# token in ?token parameter
|
||||||
@@ -307,7 +308,7 @@ async def test_hubauth_service_token(app, mockservice_url):
|
|||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
reply = r.json()
|
reply = r.json()
|
||||||
assert reply == {'kind': 'service', 'name': name, 'admin': False, 'roles': []}
|
assert service_model.items() <= reply.items()
|
||||||
|
|
||||||
r = await async_requests.get(
|
r = await async_requests.get(
|
||||||
public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
public_url(app, mockservice_url) + '/whoami/?token=no-such-token',
|
||||||
|
Reference in New Issue
Block a user