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

Unified Diff: appengine/swarming/handlers_endpoints.py

Issue 2984843002: swarming: switch to a 'capability focused' ACL system (Closed)
Patch Set: 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
Index: appengine/swarming/handlers_endpoints.py
diff --git a/appengine/swarming/handlers_endpoints.py b/appengine/swarming/handlers_endpoints.py
index f8c16342da35bef6884bbeb8600e32bae9abdcfb..69950eb7ec1d93a67257193e0a582b67070fe0c2 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 = False
+_VIEW = True
+
+
# 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.
@@ -64,8 +70,12 @@ def get_request_and_result(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)
+ if viewing == _VIEW:
+ if not acl.can_id_task_view(request.authenticated):
+ raise endpoints.ForbiddenException('%s is not accessible.' % task_id)
+ else:
+ if not acl.can_id_task_edit(request.authenticated):
+ raise endpoints.ForbiddenException('%s is not accessible.' % task_id)
return request, result
except ValueError:
raise endpoints.BadRequestException('%s is an invalid key.' % task_id)
@@ -128,7 +138,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 +166,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_bot_create)
def token(self, _request):
"""Returns a token to bootstrap a new bot."""
return swarming_rpcs.BootstrapToken(
@@ -170,21 +180,22 @@ class SwarmingServerService(remote.Service):
@auth.public
def permissions(self, _request):
"""Returns the caller's permissions."""
+ # TODO(maruel): Redo this.
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._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(),
)
@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_config_view)
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_config_view)
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_config_edit)
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_config_edit)
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_task_view)
def result(self, request):
"""Reports the result of the task corresponding to a task ID.
@@ -289,7 +300,8 @@ 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)
+ # This enforces read access.
+ _, result = get_request_and_result(request.task_id, _VIEW)
return message_conversion.task_result_to_rpc(
result, request.include_performance_stats)
@@ -299,11 +311,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_task_view)
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 +323,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_task_edit)
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 +340,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_task_view)
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 +348,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 +361,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_task_create)
def new(self, request):
"""Creates a new task.
@@ -390,7 +402,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_task_view)
def list(self, request):
"""Returns tasks results based on the filters.
@@ -428,7 +440,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_task_view)
def requests(self, request):
"""Returns tasks requests based on the filters.
@@ -468,7 +480,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_tasks_edit)
def cancel(self, request):
"""Cancel a subset of pending tasks based on the tags.
@@ -509,7 +521,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_task_view)
def count(self, request):
"""Counts number of tasks in a given state."""
logging.debug('%s', request)
@@ -549,7 +561,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_task_view)
def tags(self, _request):
"""Returns the cached set of tags currently seen in the fleet."""
tags = task_result.TagAggregation.KEY.get()
@@ -584,7 +596,7 @@ class SwarmingBotService(remote.Service):
name='get',
path='{bot_id}/get',
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_bot_view)
def get(self, request):
"""Returns information about a known bot.
@@ -627,7 +639,7 @@ class SwarmingBotService(remote.Service):
BotId, swarming_rpcs.DeletedResponse,
name='delete',
path='{bot_id}/delete')
- @auth.require(acl.is_admin)
+ @auth.require(acl.can_bot_edit)
def delete(self, request):
"""Deletes the bot corresponding to a provided bot_id.
@@ -653,7 +665,7 @@ class SwarmingBotService(remote.Service):
name='events',
path='{bot_id}/events',
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_bot_view)
def events(self, request):
"""Returns events that happened on a bot."""
logging.debug('%s', request)
@@ -685,7 +697,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_bot_edit)
def terminate(self, request):
"""Asks a bot to terminate itself gracefully.
@@ -722,7 +734,7 @@ class SwarmingBotService(remote.Service):
name='tasks',
path='{bot_id}/tasks',
http_method='GET')
- @auth.require(acl.is_privileged_user)
+ @auth.require(acl.can_task_view)
def tasks(self, request):
"""Lists a given bot's tasks within the specified date range.
@@ -765,7 +777,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_bot_view)
def list(self, request):
"""Provides list of known bots.
@@ -795,7 +807,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_bot_view)
def count(self, request):
"""Counts number of bots with given dimensions."""
logging.debug('%s', request)
@@ -821,12 +833,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_bot_view)
def dimensions(self, _request):
"""Returns the cached set of dimensions currently in use in the fleet."""
dims = bot_management.DimensionAggregation.KEY.get()

Powered by Google App Engine
This is Rietveld 408576698