mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-19 16:03:00 +00:00
Implemented scope-based access in API handlers
This commit is contained in:
@@ -7,6 +7,7 @@ from tornado import web
|
|||||||
|
|
||||||
from .. import orm
|
from .. import orm
|
||||||
from ..utils import admin_only
|
from ..utils import admin_only
|
||||||
|
from ..utils import needs_scope
|
||||||
from .base import APIHandler
|
from .base import APIHandler
|
||||||
|
|
||||||
|
|
||||||
@@ -34,13 +35,16 @@ class _GroupAPIHandler(APIHandler):
|
|||||||
|
|
||||||
|
|
||||||
class GroupListAPIHandler(_GroupAPIHandler):
|
class GroupListAPIHandler(_GroupAPIHandler):
|
||||||
@admin_only
|
@needs_scope('read:groups')
|
||||||
def get(self):
|
def get(self, subset=None):
|
||||||
"""List groups"""
|
"""List groups"""
|
||||||
data = [self.group_model(g) for g in self.db.query(orm.Group)]
|
groups = self.db.query(orm.Group)
|
||||||
|
if subset is not None:
|
||||||
|
groups = groups.filter(orm.Group.name.in_(subset))
|
||||||
|
data = [self.group_model(g) for g in groups]
|
||||||
self.write(json.dumps(data))
|
self.write(json.dumps(data))
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('admin:groups')
|
||||||
async def post(self):
|
async def post(self):
|
||||||
"""POST creates Multiple groups """
|
"""POST creates Multiple groups """
|
||||||
model = self.get_json_body()
|
model = self.get_json_body()
|
||||||
@@ -73,12 +77,14 @@ class GroupListAPIHandler(_GroupAPIHandler):
|
|||||||
class GroupAPIHandler(_GroupAPIHandler):
|
class GroupAPIHandler(_GroupAPIHandler):
|
||||||
"""View and modify groups by name"""
|
"""View and modify groups by name"""
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('read:groups')
|
||||||
def get(self, name):
|
def get(self, name, subset=None):
|
||||||
|
if subset is not None and name not in subset:
|
||||||
|
raise web.HTTPError(403, "No read access to group {}".format(name))
|
||||||
group = self.find_group(name)
|
group = self.find_group(name)
|
||||||
self.write(json.dumps(self.group_model(group)))
|
self.write(json.dumps(self.group_model(group)))
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('admin:groups')
|
||||||
async def post(self, name):
|
async def post(self, name):
|
||||||
"""POST creates a group by name"""
|
"""POST creates a group by name"""
|
||||||
model = self.get_json_body()
|
model = self.get_json_body()
|
||||||
@@ -104,9 +110,11 @@ class GroupAPIHandler(_GroupAPIHandler):
|
|||||||
self.write(json.dumps(self.group_model(group)))
|
self.write(json.dumps(self.group_model(group)))
|
||||||
self.set_status(201)
|
self.set_status(201)
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('admin:groups')
|
||||||
def delete(self, name):
|
def delete(self, name, subset=None):
|
||||||
"""Delete a group by name"""
|
"""Delete a group by name"""
|
||||||
|
if subset is not None and name not in subset:
|
||||||
|
raise web.HTTPError(403, "No write access to group {}".format(name))
|
||||||
group = self.find_group(name)
|
group = self.find_group(name)
|
||||||
self.log.info("Deleting group %s", name)
|
self.log.info("Deleting group %s", name)
|
||||||
self.db.delete(group)
|
self.db.delete(group)
|
||||||
@@ -117,9 +125,11 @@ class GroupAPIHandler(_GroupAPIHandler):
|
|||||||
class GroupUsersAPIHandler(_GroupAPIHandler):
|
class GroupUsersAPIHandler(_GroupAPIHandler):
|
||||||
"""Modify a group's user list"""
|
"""Modify a group's user list"""
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('groups')
|
||||||
def post(self, name):
|
def post(self, name, subset=None):
|
||||||
"""POST adds users to a group"""
|
"""POST adds users to a group"""
|
||||||
|
if subset is not None and name not in subset:
|
||||||
|
raise web.HTTPError(403, "No access to add users to group {}".format(name))
|
||||||
group = self.find_group(name)
|
group = self.find_group(name)
|
||||||
data = self.get_json_body()
|
data = self.get_json_body()
|
||||||
self._check_group_model(data)
|
self._check_group_model(data)
|
||||||
@@ -135,9 +145,11 @@ class GroupUsersAPIHandler(_GroupAPIHandler):
|
|||||||
self.db.commit()
|
self.db.commit()
|
||||||
self.write(json.dumps(self.group_model(group)))
|
self.write(json.dumps(self.group_model(group)))
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('groups')
|
||||||
async def delete(self, name):
|
async def delete(self, name, subset=None):
|
||||||
"""DELETE removes users from a group"""
|
"""DELETE removes users from a group"""
|
||||||
|
if subset is not None and name not in subset:
|
||||||
|
raise web.HTTPError(403, "No access to add users to group {}".format(name))
|
||||||
group = self.find_group(name)
|
group = self.find_group(name)
|
||||||
data = self.get_json_body()
|
data = self.get_json_body()
|
||||||
self._check_group_model(data)
|
self._check_group_model(data)
|
||||||
|
@@ -9,11 +9,12 @@ from tornado.ioloop import IOLoop
|
|||||||
|
|
||||||
from .._version import __version__
|
from .._version import __version__
|
||||||
from ..utils import admin_only
|
from ..utils import admin_only
|
||||||
|
from ..utils import needs_scope
|
||||||
from .base import APIHandler
|
from .base import APIHandler
|
||||||
|
|
||||||
|
|
||||||
class ShutdownAPIHandler(APIHandler):
|
class ShutdownAPIHandler(APIHandler):
|
||||||
@admin_only
|
@needs_scope('shutdown')
|
||||||
def post(self):
|
def post(self):
|
||||||
"""POST /api/shutdown triggers a clean shutdown
|
"""POST /api/shutdown triggers a clean shutdown
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ class RootAPIHandler(APIHandler):
|
|||||||
|
|
||||||
|
|
||||||
class InfoAPIHandler(APIHandler):
|
class InfoAPIHandler(APIHandler):
|
||||||
@admin_only
|
@needs_scope('admin') # Todo: Probably too strict
|
||||||
def get(self):
|
def get(self):
|
||||||
"""GET /api/info returns detailed info about the Hub and its API.
|
"""GET /api/info returns detailed info about the Hub and its API.
|
||||||
|
|
||||||
|
@@ -6,11 +6,12 @@ import json
|
|||||||
from tornado import web
|
from tornado import web
|
||||||
|
|
||||||
from ..utils import admin_only
|
from ..utils import admin_only
|
||||||
|
from ..utils import needs_scope
|
||||||
from .base import APIHandler
|
from .base import APIHandler
|
||||||
|
|
||||||
|
|
||||||
class ProxyAPIHandler(APIHandler):
|
class ProxyAPIHandler(APIHandler):
|
||||||
@admin_only
|
@needs_scope('proxy')
|
||||||
async def get(self):
|
async def get(self):
|
||||||
"""GET /api/proxy fetches the routing table
|
"""GET /api/proxy fetches the routing table
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ class ProxyAPIHandler(APIHandler):
|
|||||||
routes = await self.proxy.get_all_routes()
|
routes = await self.proxy.get_all_routes()
|
||||||
self.write(json.dumps(routes))
|
self.write(json.dumps(routes))
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('proxy')
|
||||||
async def post(self):
|
async def post(self):
|
||||||
"""POST checks the proxy to ensure that it's up to date.
|
"""POST checks the proxy to ensure that it's up to date.
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ class ProxyAPIHandler(APIHandler):
|
|||||||
"""
|
"""
|
||||||
await self.proxy.check_routes(self.users, self.services)
|
await self.proxy.check_routes(self.users, self.services)
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('proxy')
|
||||||
async def patch(self):
|
async def patch(self):
|
||||||
"""PATCH updates the location of the proxy
|
"""PATCH updates the location of the proxy
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ from tornado import web
|
|||||||
|
|
||||||
from .. import orm
|
from .. import orm
|
||||||
from ..utils import admin_only
|
from ..utils import admin_only
|
||||||
|
from ..utils import needs_scope
|
||||||
from .base import APIHandler
|
from .base import APIHandler
|
||||||
|
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ def service_model(service):
|
|||||||
|
|
||||||
|
|
||||||
class ServiceListAPIHandler(APIHandler):
|
class ServiceListAPIHandler(APIHandler):
|
||||||
@admin_only
|
@needs_scope('read:services')
|
||||||
def get(self):
|
def get(self):
|
||||||
data = {name: service_model(service) for name, service in self.services.items()}
|
data = {name: service_model(service) for name, service in self.services.items()}
|
||||||
self.write(json.dumps(data))
|
self.write(json.dumps(data))
|
||||||
@@ -56,7 +57,7 @@ def admin_or_self(method):
|
|||||||
|
|
||||||
|
|
||||||
class ServiceAPIHandler(APIHandler):
|
class ServiceAPIHandler(APIHandler):
|
||||||
@admin_or_self
|
@needs_scope('read:services')
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
service = self.services[name]
|
service = self.services[name]
|
||||||
self.write(json.dumps(service_model(service)))
|
self.write(json.dumps(service_model(service)))
|
||||||
|
@@ -14,10 +14,10 @@ from tornado.iostream import StreamClosedError
|
|||||||
|
|
||||||
from .. import orm
|
from .. import orm
|
||||||
from ..user import User
|
from ..user import User
|
||||||
from ..utils import admin_only
|
|
||||||
from ..utils import isoformat
|
from ..utils import isoformat
|
||||||
from ..utils import iterate_until
|
from ..utils import iterate_until
|
||||||
from ..utils import maybe_future
|
from ..utils import maybe_future
|
||||||
|
from ..utils import needs_scope
|
||||||
from ..utils import url_path_join
|
from ..utils import url_path_join
|
||||||
from .base import APIHandler
|
from .base import APIHandler
|
||||||
|
|
||||||
@@ -28,6 +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('read:users')
|
||||||
async def get(self):
|
async def get(self):
|
||||||
user = self.current_user
|
user = self.current_user
|
||||||
if user is None:
|
if user is None:
|
||||||
@@ -39,15 +40,19 @@ class SelfAPIHandler(APIHandler):
|
|||||||
|
|
||||||
|
|
||||||
class UserListAPIHandler(APIHandler):
|
class UserListAPIHandler(APIHandler):
|
||||||
@admin_only
|
@needs_scope('read:users')
|
||||||
def get(self):
|
def get(self, subset=None):
|
||||||
|
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)
|
self.user_model(u, include_servers=True, include_state=True) for u in users
|
||||||
for u in self.db.query(orm.User)
|
|
||||||
]
|
]
|
||||||
self.write(json.dumps(data))
|
self.write(json.dumps(data))
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('users')
|
||||||
async def post(self):
|
async def post(self):
|
||||||
data = self.get_json_body()
|
data = self.get_json_body()
|
||||||
if not data or not isinstance(data, dict) or not data.get('usernames'):
|
if not data or not isinstance(data, dict) or not data.get('usernames'):
|
||||||
@@ -122,9 +127,13 @@ def admin_or_self(method):
|
|||||||
|
|
||||||
|
|
||||||
class UserAPIHandler(APIHandler):
|
class UserAPIHandler(APIHandler):
|
||||||
@admin_or_self
|
@needs_scope('read:users')
|
||||||
async def get(self, name):
|
async def get(self, name, subset=None):
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
|
if subset is not None:
|
||||||
|
if user not in subset:
|
||||||
|
raise web.HTTPError(403, "No access to users")
|
||||||
|
|
||||||
model = self.user_model(
|
model = self.user_model(
|
||||||
user, include_servers=True, include_state=self.current_user.admin
|
user, include_servers=True, include_state=self.current_user.admin
|
||||||
)
|
)
|
||||||
@@ -137,7 +146,7 @@ class UserAPIHandler(APIHandler):
|
|||||||
model['auth_state'] = await user.get_auth_state()
|
model['auth_state'] = await user.get_auth_state()
|
||||||
self.write(json.dumps(model))
|
self.write(json.dumps(model))
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('admin:users')
|
||||||
async def post(self, name):
|
async def post(self, name):
|
||||||
data = self.get_json_body()
|
data = self.get_json_body()
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
@@ -162,7 +171,7 @@ class UserAPIHandler(APIHandler):
|
|||||||
self.write(json.dumps(self.user_model(user)))
|
self.write(json.dumps(self.user_model(user)))
|
||||||
self.set_status(201)
|
self.set_status(201)
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('admin:users')
|
||||||
async def delete(self, name):
|
async def delete(self, name):
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if user is None:
|
if user is None:
|
||||||
@@ -187,7 +196,7 @@ class UserAPIHandler(APIHandler):
|
|||||||
|
|
||||||
self.set_status(204)
|
self.set_status(204)
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('admin:users')
|
||||||
async def patch(self, name):
|
async def patch(self, name):
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if user is None:
|
if user is None:
|
||||||
@@ -215,7 +224,7 @@ class UserAPIHandler(APIHandler):
|
|||||||
class UserTokenListAPIHandler(APIHandler):
|
class UserTokenListAPIHandler(APIHandler):
|
||||||
"""API endpoint for listing/creating tokens"""
|
"""API endpoint for listing/creating tokens"""
|
||||||
|
|
||||||
@admin_or_self
|
@needs_scope('users:tokens')
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
"""Get tokens for a given user"""
|
"""Get tokens for a given user"""
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
@@ -249,6 +258,7 @@ 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):
|
||||||
@@ -313,6 +323,7 @@ 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
|
||||||
|
|
||||||
@@ -320,7 +331,7 @@ class UserTokenAPIHandler(APIHandler):
|
|||||||
(e.g. wrong owner, invalid key format, etc.)
|
(e.g. wrong owner, invalid key format, etc.)
|
||||||
"""
|
"""
|
||||||
not_found = "No such token %s for user %s" % (token_id, user.name)
|
not_found = "No such token %s for user %s" % (token_id, user.name)
|
||||||
prefix, id = token_id[0], token_id[1:]
|
prefix, id_ = token_id[0], token_id[1:]
|
||||||
if prefix == 'a':
|
if prefix == 'a':
|
||||||
Token = orm.APIToken
|
Token = orm.APIToken
|
||||||
elif prefix == 'o':
|
elif prefix == 'o':
|
||||||
@@ -328,16 +339,16 @@ class UserTokenAPIHandler(APIHandler):
|
|||||||
else:
|
else:
|
||||||
raise web.HTTPError(404, not_found)
|
raise web.HTTPError(404, not_found)
|
||||||
try:
|
try:
|
||||||
id = int(id)
|
id_ = int(id_)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise web.HTTPError(404, not_found)
|
raise web.HTTPError(404, not_found)
|
||||||
|
|
||||||
orm_token = self.db.query(Token).filter(Token.id == id).first()
|
orm_token = self.db.query(Token).filter(Token.id == id_).first()
|
||||||
if orm_token is None or orm_token.user is not user.orm_user:
|
if orm_token is None or orm_token.user is not user.orm_user:
|
||||||
raise web.HTTPError(404, "Token not found %s", orm_token)
|
raise web.HTTPError(404, "Token not found %s", orm_token)
|
||||||
return orm_token
|
return orm_token
|
||||||
|
|
||||||
@admin_or_self
|
@needs_scope('read:users:tokens')
|
||||||
def get(self, name, token_id):
|
def get(self, name, token_id):
|
||||||
""""""
|
""""""
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
@@ -346,7 +357,7 @@ class UserTokenAPIHandler(APIHandler):
|
|||||||
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)))
|
||||||
|
|
||||||
@admin_or_self
|
@needs_scope('users:tokens')
|
||||||
def delete(self, name, token_id):
|
def delete(self, name, token_id):
|
||||||
"""Delete a token"""
|
"""Delete a token"""
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
@@ -371,12 +382,17 @@ class UserTokenAPIHandler(APIHandler):
|
|||||||
class UserServerAPIHandler(APIHandler):
|
class UserServerAPIHandler(APIHandler):
|
||||||
"""Start and stop single-user servers"""
|
"""Start and stop single-user servers"""
|
||||||
|
|
||||||
@admin_or_self
|
@needs_scope('user:servers')
|
||||||
async def post(self, name, server_name=''):
|
async def post(self, name, server_name='', subset=None):
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if server_name:
|
if server_name:
|
||||||
if not self.allow_named_servers:
|
if not self.allow_named_servers:
|
||||||
raise web.HTTPError(400, "Named servers are not enabled.")
|
raise web.HTTPError(400, "Named servers are not enabled.")
|
||||||
|
if subset is not None:
|
||||||
|
if server_name not in subset:
|
||||||
|
raise web.HTTPError(
|
||||||
|
403, "No access to create {}".format(server_name)
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
self.named_server_limit_per_user > 0
|
self.named_server_limit_per_user > 0
|
||||||
and server_name not in user.orm_spawners
|
and server_name not in user.orm_spawners
|
||||||
@@ -416,7 +432,7 @@ class UserServerAPIHandler(APIHandler):
|
|||||||
self.set_header('Content-Type', 'text/plain')
|
self.set_header('Content-Type', 'text/plain')
|
||||||
self.set_status(status)
|
self.set_status(status)
|
||||||
|
|
||||||
@admin_or_self
|
@needs_scope('user:servers')
|
||||||
async def delete(self, name, server_name=''):
|
async def delete(self, name, server_name=''):
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
options = self.get_json_body()
|
options = self.get_json_body()
|
||||||
@@ -479,7 +495,7 @@ class UserAdminAccessAPIHandler(APIHandler):
|
|||||||
This handler sets the necessary cookie for an admin to login to a single-user server.
|
This handler sets the necessary cookie for an admin to login to a single-user server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@admin_only
|
@needs_scope('users:servers')
|
||||||
def post(self, name):
|
def post(self, name):
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"Deprecated in JupyterHub 0.8."
|
"Deprecated in JupyterHub 0.8."
|
||||||
@@ -535,11 +551,16 @@ class SpawnProgressAPIHandler(APIHandler):
|
|||||||
|
|
||||||
await asyncio.wait([self._finish_future], timeout=self.keepalive_interval)
|
await asyncio.wait([self._finish_future], timeout=self.keepalive_interval)
|
||||||
|
|
||||||
@admin_or_self
|
@needs_scope('read:users:servers')
|
||||||
async def get(self, username, server_name=''):
|
async def get(self, username, server_name='', subset=None):
|
||||||
self.set_header('Cache-Control', 'no-cache')
|
self.set_header('Cache-Control', 'no-cache')
|
||||||
if server_name is None:
|
if server_name is None:
|
||||||
server_name = ''
|
server_name = ''
|
||||||
|
if subset is not None:
|
||||||
|
if server_name not in subset:
|
||||||
|
raise web.HTTPError(
|
||||||
|
403, "No read access to server {}".format(server_name)
|
||||||
|
)
|
||||||
user = self.find_user(username)
|
user = self.find_user(username)
|
||||||
if user is None:
|
if user is None:
|
||||||
# no such user
|
# no such user
|
||||||
@@ -678,7 +699,7 @@ class ActivityAPIHandler(APIHandler):
|
|||||||
)
|
)
|
||||||
return servers
|
return servers
|
||||||
|
|
||||||
@admin_or_self
|
@needs_scope('users')
|
||||||
def post(self, username):
|
def post(self, username):
|
||||||
user = self.find_user(username)
|
user = self.find_user(username)
|
||||||
if user is None:
|
if user is None:
|
||||||
|
@@ -8,6 +8,7 @@ import hashlib
|
|||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import socket
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
@@ -247,9 +248,10 @@ def auth_decorator(check_auth):
|
|||||||
|
|
||||||
def decorator(method):
|
def decorator(method):
|
||||||
def decorated(self, *args, **kwargs):
|
def decorated(self, *args, **kwargs):
|
||||||
check_auth(self)
|
check_auth(self, **kwargs)
|
||||||
return method(self, *args, **kwargs)
|
return method(self, *args, **kwargs)
|
||||||
|
|
||||||
|
# Perhaps replace with functools.wrap
|
||||||
decorated.__name__ = method.__name__
|
decorated.__name__ = method.__name__
|
||||||
decorated.__doc__ = method.__doc__
|
decorated.__doc__ = method.__doc__
|
||||||
return decorated
|
return decorated
|
||||||
@@ -296,6 +298,20 @@ def metrics_authentication(self):
|
|||||||
raise web.HTTPError(403)
|
raise web.HTTPError(403)
|
||||||
|
|
||||||
|
|
||||||
|
@auth_decorator
|
||||||
|
def needs_scope(self, scope, **kwargs):
|
||||||
|
"""Decorator to restrict access to users or services with the required scope"""
|
||||||
|
if scope not in self.current_scopes:
|
||||||
|
# Check if access is not restricted to user/server/group
|
||||||
|
match_string = re.compile("^" + re.escape(scope) + r"!.+=.+$")
|
||||||
|
subscopes = filter(lambda s: re.search(match_string, s), self.current_scopes)
|
||||||
|
subset = [subscope.split('=')[1] for subscope in subscopes]
|
||||||
|
if not subset:
|
||||||
|
raise web.HTTPError(403, "Action is not authorized with current scopes")
|
||||||
|
else:
|
||||||
|
kwargs['subset'] = subset
|
||||||
|
|
||||||
|
|
||||||
# Token utilities
|
# Token utilities
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user