Fixed scoping and authentication

This commit is contained in:
0mar
2020-10-30 15:07:10 +01:00
parent 496832d7b4
commit 422fbf8dcc
3 changed files with 22 additions and 25 deletions

View File

@@ -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):

View File

@@ -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 = []

View File

@@ -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