Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(995)

Unified Diff: appengine/swarming/handlers_endpoints.py

Issue 2984843002: swarming: switch to a 'capability focused' ACL system (Closed)
Patch Set: Tuned permissions, added tests Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/swarming/handlers_bot.py ('k') | appengine/swarming/handlers_endpoints_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/swarming/handlers_endpoints.py
diff --git a/appengine/swarming/handlers_endpoints.py b/appengine/swarming/handlers_endpoints.py
index f8c16342da35bef6884bbeb8600e32bae9abdcfb..498539b1777724a35fe93950381a5b59d58bb470 100644
--- a/appengine/swarming/handlers_endpoints.py
+++ b/appengine/swarming/handlers_endpoints.py
@@ -40,6 +40,12 @@ from server import task_scheduler
### Helper Methods
+# Used by get_request_and_result(), clearer than using True/False and important
+# as this is part of the security boundary.
+_EDIT = object()
+_VIEW = object()
+
+
# Add support for BooleanField in protorpc in endpoints GET requests.
_old_decode_field = protojson.ProtoJson.decode_field
def _decode_field(self, field, value):
@@ -50,7 +56,7 @@ def _decode_field(self, field, value):
protojson.ProtoJson.decode_field = _decode_field
-def get_request_and_result(task_id):
+def get_request_and_result(task_id, viewing):
"""Provides the key and TaskRequest corresponding to a task ID.
Enforces the ACL for users. Allows bots all access for the moment.
@@ -62,13 +68,19 @@ def get_request_and_result(task_id):
try:
request_key, result_key = task_pack.get_request_and_result_keys(task_id)
request, result = ndb.get_multi((request_key, result_key))
- if not request or not result:
- raise endpoints.NotFoundException('%s not found.' % task_id)
- if not acl.is_bot() and not request.has_access:
- raise endpoints.ForbiddenException('%s is not accessible.' % task_id)
- return request, result
except ValueError:
raise endpoints.BadRequestException('%s is an invalid key.' % task_id)
+ if not request or not result:
+ raise endpoints.NotFoundException('%s not found.' % task_id)
+ if viewing == _VIEW:
+ if not acl.can_view_task(request):
+ raise endpoints.ForbiddenException('%s is not accessible.' % task_id)
+ elif viewing == _EDIT:
+ if not acl.can_edit_task(request):
+ raise endpoints.ForbiddenException('%s is not accessible.' % task_id)
+ else:
+ raise endpoints.InternalServerErrorException('get_request_and_result()')
+ return request, result
def get_or_raise(key):
@@ -128,7 +140,7 @@ class SwarmingServerService(remote.Service):
@auth.endpoints_method(
message_types.VoidMessage, swarming_rpcs.ServerDetails,
http_method='GET')
- @auth.require(acl.is_bot_or_user)
+ @auth.require(acl.can_access)
def details(self, _request):
"""Returns information about the server."""
host = 'https://' + os.environ['HTTP_HOST']
@@ -156,7 +168,7 @@ class SwarmingServerService(remote.Service):
@gae_ts_mon.instrument_endpoint()
@auth.endpoints_method(
message_types.VoidMessage, swarming_rpcs.BootstrapToken)
- @auth.require(acl.is_bootstrapper)
+ @auth.require(acl.can_create_bot)
def token(self, _request):
"""Returns a token to bootstrap a new bot."""
return swarming_rpcs.BootstrapToken(
@@ -171,20 +183,19 @@ class SwarmingServerService(remote.Service):
def permissions(self, _request):
"""Returns the caller's permissions."""
return swarming_rpcs.ClientPermissions(
- delete_bot = acl.is_admin(),
- terminate_bot = acl.is_privileged_user(),
- get_configs = acl.is_user(),
- put_configs = acl.is_admin(),
- cancel_task = acl.is_user(),
- cancel_tasks = acl.is_admin(),
- get_bootstrap_token = acl.is_bootstrapper(),
- )
+ delete_bot=acl.can_delete_bot(),
+ terminate_bot=acl.can_edit_bot(),
+ get_configs=acl.can_view_config(),
+ put_configs=acl.can_edit_config(),
+ cancel_task=acl._is_user() or acl.is_ip_whitelisted_machine(),
+ cancel_tasks=acl.can_edit_all_tasks(),
+ get_bootstrap_token=acl.can_create_bot())
@gae_ts_mon.instrument_endpoint()
@auth.endpoints_method(
VersionRequest, swarming_rpcs.FileContent,
http_method='GET')
- @auth.require(acl.is_bot_or_user)
+ @auth.require(acl.can_view_config)
def get_bootstrap(self, request):
"""Retrieves the current or a previous version of bootstrap.py.
@@ -204,7 +215,7 @@ class SwarmingServerService(remote.Service):
@auth.endpoints_method(
VersionRequest, swarming_rpcs.FileContent,
http_method='GET')
- @auth.require(acl.is_bot_or_user)
+ @auth.require(acl.can_view_config)
def get_bot_config(self, request):
"""Retrieves the current or a previous version of bot_config.py.
@@ -223,7 +234,7 @@ class SwarmingServerService(remote.Service):
@gae_ts_mon.instrument_endpoint()
@auth.endpoints_method(
swarming_rpcs.FileContentRequest, swarming_rpcs.FileContent)
- @auth.require(acl.is_admin)
+ @auth.require(acl.can_edit_config)
def put_bootstrap(self, request):
"""Stores a new version of bootstrap.py.
@@ -241,7 +252,7 @@ class SwarmingServerService(remote.Service):
@gae_ts_mon.instrument_endpoint()
@auth.endpoints_method(
swarming_rpcs.FileContentRequest, swarming_rpcs.FileContent)
- @auth.require(acl.is_admin)
+ @auth.require(acl.can_edit_config)
def put_bot_config(self, request):
"""Stores a new version of bot_config.py.
@@ -278,7 +289,7 @@ class SwarmingTaskService(remote.Service):
name='result',
path='{task_id}/result',
http_method='GET')
- @auth.require(acl.is_bot_or_user)
+ @auth.require(acl.can_access)
def result(self, request):
"""Reports the result of the task corresponding to a task ID.
@@ -289,7 +300,7 @@ class SwarmingTaskService(remote.Service):
A summary ID ends with '0', a run ID ends with '1' or '2'.
"""
logging.debug('%s', request)
- _, result = get_request_and_result(request.task_id)
+ _, result = get_request_and_result(request.task_id, _VIEW)
return message_conversion.task_result_to_rpc(
result, request.include_performance_stats)
@@ -299,11 +310,11 @@ class SwarmingTaskService(remote.Service):
name='request',
path='{task_id}/request',
http_method='GET')
- @auth.require(acl.is_bot_or_user)
+ @auth.require(acl.can_access)
def request(self, request):
"""Returns the task request corresponding to a task ID."""
logging.debug('%s', request)
- request_obj, _ = get_request_and_result(request.task_id)
+ request_obj, _ = get_request_and_result(request.task_id, _VIEW)
return message_conversion.task_request_to_rpc(request_obj)
@gae_ts_mon.instrument_endpoint()
@@ -311,14 +322,14 @@ class SwarmingTaskService(remote.Service):
TaskId, swarming_rpcs.CancelResponse,
name='cancel',
path='{task_id}/cancel')
- @auth.require(acl.is_bot_or_user)
+ @auth.require(acl.can_access)
def cancel(self, request):
"""Cancels a task.
If a bot was running the task, the bot will forcibly cancel the task.
"""
logging.debug('%s', request)
- request_obj, result = get_request_and_result(request.task_id)
+ request_obj, result = get_request_and_result(request.task_id, _EDIT)
ok, was_running = task_scheduler.cancel_task(request_obj, result.key)
return swarming_rpcs.CancelResponse(ok=ok, was_running=was_running)
@@ -328,7 +339,7 @@ class SwarmingTaskService(remote.Service):
name='stdout',
path='{task_id}/stdout',
http_method='GET')
- @auth.require(acl.is_bot_or_user)
+ @auth.require(acl.can_access)
def stdout(self, request):
"""Returns the output of the task corresponding to a task ID."""
# TODO(maruel): Add streaming. Real streaming is not supported by AppEngine
@@ -336,7 +347,7 @@ class SwarmingTaskService(remote.Service):
# TODO(maruel): Send as raw content instead of encoded. This is not
# supported by cloud endpoints.
logging.debug('%s', request)
- _, result = get_request_and_result(request.task_id)
+ _, result = get_request_and_result(request.task_id, _VIEW)
output = result.get_output()
if output:
output = output.decode('utf-8', 'replace')
@@ -349,7 +360,7 @@ class SwarmingTasksService(remote.Service):
@gae_ts_mon.instrument_endpoint()
@auth.endpoints_method(
swarming_rpcs.NewTaskRequest, swarming_rpcs.TaskRequestMetadata)
- @auth.require(acl.is_bot_or_user)
+ @auth.require(acl.can_create_task)
def new(self, request):
"""Creates a new task.
@@ -390,7 +401,7 @@ class SwarmingTasksService(remote.Service):
@auth.endpoints_method(
swarming_rpcs.TasksRequest, swarming_rpcs.TaskList,
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_all_tasks)
def list(self, request):
"""Returns tasks results based on the filters.
@@ -428,7 +439,7 @@ class SwarmingTasksService(remote.Service):
@auth.endpoints_method(
swarming_rpcs.TasksRequest, swarming_rpcs.TaskRequests,
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_all_tasks)
def requests(self, request):
"""Returns tasks requests based on the filters.
@@ -468,7 +479,7 @@ class SwarmingTasksService(remote.Service):
@auth.endpoints_method(
swarming_rpcs.TasksCancelRequest, swarming_rpcs.TasksCancelResponse,
http_method='POST')
- @auth.require(acl.is_admin)
+ @auth.require(acl.can_edit_all_tasks)
def cancel(self, request):
"""Cancel a subset of pending tasks based on the tags.
@@ -509,7 +520,7 @@ class SwarmingTasksService(remote.Service):
@auth.endpoints_method(
swarming_rpcs.TasksCountRequest, swarming_rpcs.TasksCount,
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_all_tasks)
def count(self, request):
"""Counts number of tasks in a given state."""
logging.debug('%s', request)
@@ -549,7 +560,7 @@ class SwarmingTasksService(remote.Service):
@auth.endpoints_method(
message_types.VoidMessage, swarming_rpcs.TasksTags,
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_all_tasks)
def tags(self, _request):
"""Returns the cached set of tags currently seen in the fleet."""
tags = task_result.TagAggregation.KEY.get()
@@ -584,7 +595,7 @@ class SwarmingBotService(remote.Service):
name='get',
path='{bot_id}/get',
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_bot)
def get(self, request):
"""Returns information about a known bot.
@@ -627,7 +638,7 @@ class SwarmingBotService(remote.Service):
BotId, swarming_rpcs.DeletedResponse,
name='delete',
path='{bot_id}/delete')
- @auth.require(acl.is_admin)
+ @auth.require(acl.can_delete_bot)
def delete(self, request):
"""Deletes the bot corresponding to a provided bot_id.
@@ -653,7 +664,7 @@ class SwarmingBotService(remote.Service):
name='events',
path='{bot_id}/events',
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_bot)
def events(self, request):
"""Returns events that happened on a bot."""
logging.debug('%s', request)
@@ -685,7 +696,7 @@ class SwarmingBotService(remote.Service):
BotId, swarming_rpcs.TerminateResponse,
name='terminate',
path='{bot_id}/terminate')
- @auth.require(acl.is_bot_or_privileged_user)
+ @auth.require(acl.can_edit_bot)
def terminate(self, request):
"""Asks a bot to terminate itself gracefully.
@@ -722,7 +733,7 @@ class SwarmingBotService(remote.Service):
name='tasks',
path='{bot_id}/tasks',
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_all_tasks)
def tasks(self, request):
"""Lists a given bot's tasks within the specified date range.
@@ -765,7 +776,7 @@ class SwarmingBotsService(remote.Service):
@auth.endpoints_method(
swarming_rpcs.BotsRequest, swarming_rpcs.BotList,
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_bot)
def list(self, request):
"""Provides list of known bots.
@@ -795,7 +806,7 @@ class SwarmingBotsService(remote.Service):
@auth.endpoints_method(
swarming_rpcs.BotsRequest, swarming_rpcs.BotsCount,
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_bot)
def count(self, request):
"""Counts number of bots with given dimensions."""
logging.debug('%s', request)
@@ -821,12 +832,11 @@ class SwarmingBotsService(remote.Service):
busy=f_busy.get_result(),
now=now)
-
@gae_ts_mon.instrument_endpoint()
@auth.endpoints_method(
message_types.VoidMessage, swarming_rpcs.BotsDimensions,
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_view_bot)
def dimensions(self, _request):
"""Returns the cached set of dimensions currently in use in the fleet."""
dims = bot_management.DimensionAggregation.KEY.get()
« no previous file with comments | « appengine/swarming/handlers_bot.py ('k') | appengine/swarming/handlers_endpoints_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698