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
ARG JUPYTERHUB_VERSION=0.8.1
RUN pip3 install --no-cache jupyterhub==${JUPYTERHUB_VERSION}
FROM alpine:3.13
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
CMD ["jupyterhub"]

View File

@@ -13,8 +13,8 @@ from oauthlib import oauth2
from tornado import web
from .. import orm
from .. import scopes
from ..user import User
from ..utils import compare_token
from ..utils import token_authenticated
from .base import APIHandler
from .base import BaseHandler
@@ -23,12 +23,24 @@ from .base import BaseHandler
class TokenAPIHandler(APIHandler):
@token_authenticated
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)
if orm_token is None:
orm_token = orm.OAuthAccessToken.find(self.db, token)
if orm_token is None:
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
now = orm_token.last_activity = datetime.utcnow()
if orm_token.user:

View File

@@ -185,3 +185,25 @@ def needs_scope(*scopes):
return _auth_func
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):
self.raw_scopes = {'users'}
self.parsed_scopes = {}
self.request = mock.Mock(spec=HTTPServerRequest)
self.request.path = '/path'
@needs_scope('users')
def user_thing(self, user_name):
@@ -169,7 +171,6 @@ class MockAPIHandler:
def test_scope_method_access(scopes, method, arguments, is_allowed):
obj = MockAPIHandler()
obj.current_user = mock.Mock(name=arguments[0])
obj.request = mock.Mock(spec=HTTPServerRequest)
obj.raw_scopes = set(scopes)
obj.parsed_scopes = parse_scopes(obj.raw_scopes)
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():
obj = MockAPIHandler()
obj.current_user = mock.Mock(name='lucille')
obj.request = mock.Mock(spec=HTTPServerRequest)
obj.raw_scopes = {'users', 'read:services'}
obj.parsed_scopes = parse_scopes(obj.raw_scopes)
assert obj.secret_thing()
@@ -192,7 +192,6 @@ def test_double_scoped_method_succeeds():
def test_double_scoped_method_denials():
obj = MockAPIHandler()
obj.current_user = mock.Mock(name='lucille2')
obj.request = mock.Mock(spec=HTTPServerRequest)
obj.raw_scopes = {'users', 'read:groups'}
obj.parsed_scopes = parse_scopes(obj.raw_scopes)
with pytest.raises(web.HTTPError):