Merge branch 'rbac' into rbac-group-roles to fix CircleCI test

This commit is contained in:
IvanaH8
2021-03-24 14:30:40 +01:00
4 changed files with 47 additions and 9 deletions

View File

@@ -1,9 +1,14 @@
FROM python:3.6.3-alpine3.6 FROM alpine:3.13
ARG JUPYTERHUB_VERSION=0.8.1
RUN pip3 install --no-cache jupyterhub==${JUPYTERHUB_VERSION}
ENV LANG=en_US.UTF-8 ENV LANG=en_US.UTF-8
RUN apk add --no-cache \
python3 \
py3-pip \
py3-ruamel.yaml \
py3-cryptography \
py3-sqlalchemy
ARG JUPYTERHUB_VERSION=1.3.0
RUN pip3 install --no-cache jupyterhub==${JUPYTERHUB_VERSION}
USER nobody USER nobody
CMD ["jupyterhub"] CMD ["jupyterhub"]

View File

@@ -13,8 +13,8 @@ from oauthlib import oauth2
from tornado import web from tornado import web
from .. import orm from .. import orm
from .. import scopes
from ..user import User from ..user import User
from ..utils import compare_token
from ..utils import token_authenticated from ..utils import token_authenticated
from .base import APIHandler from .base import APIHandler
from .base import BaseHandler from .base import BaseHandler
@@ -23,12 +23,24 @@ from .base import BaseHandler
class TokenAPIHandler(APIHandler): class TokenAPIHandler(APIHandler):
@token_authenticated @token_authenticated
def get(self, token): def get(self, token):
# FIXME: deprecate this API for oauth token resolution, in favor of using /api/user
# TODO: require specific scope for this deprecated API, applied to oauth client secrets only?
self.log.warning(
"/authorizations/token/:token endpoint is deprecated in JupyterHub 2.0. Use /api/user"
)
orm_token = orm.APIToken.find(self.db, token) orm_token = orm.APIToken.find(self.db, token)
if orm_token is None: if orm_token is None:
orm_token = orm.OAuthAccessToken.find(self.db, token) orm_token = orm.OAuthAccessToken.find(self.db, token)
if orm_token is None: if orm_token is None:
raise web.HTTPError(404) raise web.HTTPError(404)
owner = orm_token.user or orm_token.service
if owner:
# having a token means we should be able to read the owner's model
# (this is the only thing this handler is for)
self.raw_scopes.update(scopes.identify_scopes(owner))
self.parsed_scopes = scopes.parse_scopes(self.raw_scopes)
# record activity whenever we see a token # record activity whenever we see a token
now = orm_token.last_activity = datetime.utcnow() now = orm_token.last_activity = datetime.utcnow()
if orm_token.user: if orm_token.user:

View File

@@ -185,3 +185,25 @@ def needs_scope(*scopes):
return _auth_func return _auth_func
return scope_decorator return scope_decorator
def identify_scopes(obj):
"""Return 'identify' scopes for an orm object
Arguments:
obj: orm.User or orm.Service
Returns:
scopes (set): set of scopes needed for 'identify' endpoints
"""
if isinstance(obj, orm.User):
return {
f"read:users:{field}!user={obj.name}"
for field in {"name", "admin", "groups"}
}
elif isinstance(obj, orm.Service):
return {
f"read:services:{field}!service={obj.name}" for field in {"name", "admin"}
}
else:
raise TypeError(f"Expected orm.User or orm.Service, got {obj!r}")

View File

@@ -85,6 +85,8 @@ class MockAPIHandler:
def __init__(self): def __init__(self):
self.raw_scopes = {'users'} self.raw_scopes = {'users'}
self.parsed_scopes = {} self.parsed_scopes = {}
self.request = mock.Mock(spec=HTTPServerRequest)
self.request.path = '/path'
@needs_scope('users') @needs_scope('users')
def user_thing(self, user_name): def user_thing(self, user_name):
@@ -169,7 +171,6 @@ class MockAPIHandler:
def test_scope_method_access(scopes, method, arguments, is_allowed): def test_scope_method_access(scopes, method, arguments, is_allowed):
obj = MockAPIHandler() obj = MockAPIHandler()
obj.current_user = mock.Mock(name=arguments[0]) obj.current_user = mock.Mock(name=arguments[0])
obj.request = mock.Mock(spec=HTTPServerRequest)
obj.raw_scopes = set(scopes) obj.raw_scopes = set(scopes)
obj.parsed_scopes = parse_scopes(obj.raw_scopes) obj.parsed_scopes = parse_scopes(obj.raw_scopes)
api_call = getattr(obj, method) api_call = getattr(obj, method)
@@ -183,7 +184,6 @@ def test_scope_method_access(scopes, method, arguments, is_allowed):
def test_double_scoped_method_succeeds(): def test_double_scoped_method_succeeds():
obj = MockAPIHandler() obj = MockAPIHandler()
obj.current_user = mock.Mock(name='lucille') obj.current_user = mock.Mock(name='lucille')
obj.request = mock.Mock(spec=HTTPServerRequest)
obj.raw_scopes = {'users', 'read:services'} obj.raw_scopes = {'users', 'read:services'}
obj.parsed_scopes = parse_scopes(obj.raw_scopes) obj.parsed_scopes = parse_scopes(obj.raw_scopes)
assert obj.secret_thing() assert obj.secret_thing()
@@ -192,7 +192,6 @@ def test_double_scoped_method_succeeds():
def test_double_scoped_method_denials(): def test_double_scoped_method_denials():
obj = MockAPIHandler() obj = MockAPIHandler()
obj.current_user = mock.Mock(name='lucille2') obj.current_user = mock.Mock(name='lucille2')
obj.request = mock.Mock(spec=HTTPServerRequest)
obj.raw_scopes = {'users', 'read:groups'} obj.raw_scopes = {'users', 'read:groups'}
obj.parsed_scopes = parse_scopes(obj.raw_scopes) obj.parsed_scopes = parse_scopes(obj.raw_scopes)
with pytest.raises(web.HTTPError): with pytest.raises(web.HTTPError):