mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-19 16:03:00 +00:00
Fixed scoping and authentication
This commit is contained in:
@@ -28,7 +28,7 @@ class SelfAPIHandler(APIHandler):
|
|||||||
Based on the authentication info. Acts as a 'whoami' for auth tokens.
|
Based on the authentication info. Acts as a 'whoami' for auth tokens.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@needs_scope('all')
|
# @needs_scope('read:users') # Should be read:users:user=username
|
||||||
async def get(self):
|
async def get(self):
|
||||||
user = self.current_user
|
user = self.current_user
|
||||||
if user is None:
|
if user is None:
|
||||||
@@ -41,12 +41,8 @@ class SelfAPIHandler(APIHandler):
|
|||||||
|
|
||||||
class UserListAPIHandler(APIHandler):
|
class UserListAPIHandler(APIHandler):
|
||||||
@needs_scope('read:users')
|
@needs_scope('read:users')
|
||||||
def get(self, subset=None):
|
def get(self):
|
||||||
users = self.db.query(orm.User)
|
users = self.db.query(orm.User)
|
||||||
if subset is not None:
|
|
||||||
users = users.filter(
|
|
||||||
User.name.in_(subset)
|
|
||||||
) # Should result in only one db query
|
|
||||||
data = [
|
data = [
|
||||||
self.user_model(u, include_servers=True, include_state=True) for u in users
|
self.user_model(u, include_servers=True, include_state=True) for u in users
|
||||||
]
|
]
|
||||||
@@ -131,7 +127,7 @@ class UserAPIHandler(APIHandler):
|
|||||||
async def get(self, name, subset=None):
|
async def get(self, name, subset=None):
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if subset is not None:
|
if subset is not None:
|
||||||
if user not in subset:
|
if name not in subset:
|
||||||
raise web.HTTPError(403, "No access to users")
|
raise web.HTTPError(403, "No access to users")
|
||||||
|
|
||||||
model = self.user_model(
|
model = self.user_model(
|
||||||
@@ -224,12 +220,14 @@ class UserAPIHandler(APIHandler):
|
|||||||
class UserTokenListAPIHandler(APIHandler):
|
class UserTokenListAPIHandler(APIHandler):
|
||||||
"""API endpoint for listing/creating tokens"""
|
"""API endpoint for listing/creating tokens"""
|
||||||
|
|
||||||
@needs_scope('users:tokens')
|
@needs_scope('read:users:tokens')
|
||||||
def get(self, name):
|
def get(self, name, subset=None):
|
||||||
"""Get tokens for a given user"""
|
"""Get tokens for a given user"""
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if not user:
|
if not user:
|
||||||
raise web.HTTPError(404, "No such user: %s" % name)
|
raise web.HTTPError(404, "No such user: %s" % name)
|
||||||
|
if subset is not None and name not in subset:
|
||||||
|
raise web.HTTPError(403, "No access to tokens")
|
||||||
|
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
|
|
||||||
@@ -258,7 +256,6 @@ class UserTokenListAPIHandler(APIHandler):
|
|||||||
oauth_tokens.append(self.token_model(token))
|
oauth_tokens.append(self.token_model(token))
|
||||||
self.write(json.dumps({'api_tokens': api_tokens, 'oauth_tokens': oauth_tokens}))
|
self.write(json.dumps({'api_tokens': api_tokens, 'oauth_tokens': oauth_tokens}))
|
||||||
|
|
||||||
@needs_scope('users:tokens')
|
|
||||||
async def post(self, name):
|
async def post(self, name):
|
||||||
body = self.get_json_body() or {}
|
body = self.get_json_body() or {}
|
||||||
if not isinstance(body, dict):
|
if not isinstance(body, dict):
|
||||||
@@ -323,7 +320,6 @@ class UserTokenListAPIHandler(APIHandler):
|
|||||||
class UserTokenAPIHandler(APIHandler):
|
class UserTokenAPIHandler(APIHandler):
|
||||||
"""API endpoint for retrieving/deleting individual tokens"""
|
"""API endpoint for retrieving/deleting individual tokens"""
|
||||||
|
|
||||||
@needs_scope('read:users:tokens')
|
|
||||||
def find_token_by_id(self, user, token_id):
|
def find_token_by_id(self, user, token_id):
|
||||||
"""Find a token object by token-id key
|
"""Find a token object by token-id key
|
||||||
|
|
||||||
@@ -349,20 +345,24 @@ class UserTokenAPIHandler(APIHandler):
|
|||||||
return orm_token
|
return orm_token
|
||||||
|
|
||||||
@needs_scope('read:users:tokens')
|
@needs_scope('read:users:tokens')
|
||||||
def get(self, name, token_id):
|
def get(self, name, token_id, subset=None):
|
||||||
""""""
|
""""""
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if not user:
|
if not user:
|
||||||
raise web.HTTPError(404, "No such user: %s" % name)
|
raise web.HTTPError(404, "No such user: %s" % name)
|
||||||
|
if subset is not None and name not in subset:
|
||||||
|
raise web.HTTPError(403, "No token access allowed")
|
||||||
token = self.find_token_by_id(user, token_id)
|
token = self.find_token_by_id(user, token_id)
|
||||||
self.write(json.dumps(self.token_model(token)))
|
self.write(json.dumps(self.token_model(token)))
|
||||||
|
|
||||||
@needs_scope('users:tokens')
|
@needs_scope('users:tokens')
|
||||||
def delete(self, name, token_id):
|
def delete(self, name, token_id, subset=None):
|
||||||
"""Delete a token"""
|
"""Delete a token"""
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if not user:
|
if not user:
|
||||||
raise web.HTTPError(404, "No such user: %s" % name)
|
raise web.HTTPError(404, "No such user: %s" % name)
|
||||||
|
if subset is not None and name not in subset:
|
||||||
|
raise web.HTTPError(403, "No token access allowed")
|
||||||
token = self.find_token_by_id(user, token_id)
|
token = self.find_token_by_id(user, token_id)
|
||||||
# deleting an oauth token deletes *all* oauth tokens for that client
|
# deleting an oauth token deletes *all* oauth tokens for that client
|
||||||
if isinstance(token, orm.OAuthAccessToken):
|
if isinstance(token, orm.OAuthAccessToken):
|
||||||
|
@@ -427,7 +427,7 @@ class BaseHandler(RequestHandler):
|
|||||||
# don't let errors here raise more than once
|
# don't let errors here raise more than once
|
||||||
self._jupyterhub_user = None
|
self._jupyterhub_user = None
|
||||||
self.log.exception("Error getting current user")
|
self.log.exception("Error getting current user")
|
||||||
if self._jupyterhub_user is not None:
|
if self._jupyterhub_user is not None or self.get_current_user_oauth_token():
|
||||||
self.scopes = self.settings.get("mock_scopes", [])
|
self.scopes = self.settings.get("mock_scopes", [])
|
||||||
else:
|
else:
|
||||||
self.scopes = []
|
self.scopes = []
|
||||||
|
@@ -305,21 +305,18 @@ def needs_scope(scope):
|
|||||||
def scope_decorator(func):
|
def scope_decorator(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def _auth_func(self, *args, **kwargs):
|
def _auth_func(self, *args, **kwargs):
|
||||||
self.log.warning("Scope needed: " + scope)
|
allows_subset = 'subset' in func.__code__.co_varnames
|
||||||
self.log.warning("Scope possessed: %s" % ", ".join(self.scopes))
|
if scope in self.scopes:
|
||||||
if scope not in self.scopes:
|
return func(self, *args, **kwargs)
|
||||||
# Check if access is not restricted to user/server/group
|
elif allows_subset:
|
||||||
|
# Check if access is not restricted to user/server/service
|
||||||
match_string = re.compile("^" + re.escape(scope) + r"!.+=.+$")
|
match_string = re.compile("^" + re.escape(scope) + r"!.+=.+$")
|
||||||
subscopes = filter(lambda s: re.search(match_string, s), self.scopes)
|
subscopes = filter(lambda s: re.search(match_string, s), self.scopes)
|
||||||
subset = [subscope.split('=')[1] for subscope in subscopes]
|
subset = [subscope.split('=')[1] for subscope in subscopes]
|
||||||
if not subset:
|
if subset:
|
||||||
raise web.HTTPError(
|
|
||||||
403, "Action is not authorized with current scopes"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
kwargs['subset'] = subset
|
kwargs['subset'] = subset
|
||||||
result = func(self, *args, **kwargs)
|
return func(self, *args, **kwargs)
|
||||||
return result
|
raise web.HTTPError(403, "Action is not authorized with current scopes")
|
||||||
|
|
||||||
return _auth_func
|
return _auth_func
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user