Skip to content

Commit 9b17acc

Browse files
author
Joe Stubbs
committed
resolve merge conflicts.
2 parents f67e620 + 6e72b02 commit 9b17acc

File tree

12 files changed

+354
-31
lines changed

12 files changed

+354
-31
lines changed

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4-
## 1.5.0 - 2019-09-16 (target 2019-11-01)
4+
## 1.5.0 - 2019-10-29
55
### Added
6+
- Added an endpoint `PUT /actors/aliases/{alias}` for updating the
7+
definition of an alias. Requires `UPDATE` permission for the alias as well as for the actor to which the alias should be defined.
68

79
### Changed
10+
- Fixed a bug where nonces defined for aliases would not be honored when using the alias in the URL (they were only honored when using the actor id assigned to the alias).
11+
- Fixed issue where autoscaler did not properly scale down worker pools for actors with the `sync` hint. They are now scaled down to 1.
12+
- The permission check on all on all `/aliases/{alias}` endpoints has been updated to require UPDATE on the associated `actor_id`.
813
- Fixed issue where the actor's token attribute was not being processed correctly causing tokens to be generated even for actors for which the attribute was false.
914
- Fixed issue where hypyerlinks in response model for executions were not generated correctly, showing the actor's internal database id instead of the human readable id.
10-
15+
- Fixed error messaging when using a nonce and the API endpoint+HTTP verb combination do not exist.
16+
- The admin role is now recognized when checking access to certain objects in some edge cases, including when a nonce is used.
1117

1218
### Removed
13-
- No change.
19+
- It is no longer possible to create an alias nonce for permission levels UPDATE.
1420

1521

1622
## 1.4.0 - 2019-09-16

actors/auth.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ def check_nonce():
8484
any privileged action cannot be taken via a nonce.
8585
"""
8686
logger.debug("top of check_nonce")
87+
# first check whether the request is even valid -
88+
if hasattr(request, 'url_rule'):
89+
logger.debug("request.url_rule: {}".format(request.url_rule))
90+
if hasattr(request.url_rule, 'rule'):
91+
logger.debug("url_rule.rule: {}".format(request.url_rule.rule))
92+
else:
93+
logger.info("url_rule has no rule.")
94+
raise ResourceError(
95+
"Invalid request: the API endpoint does not exist or the provided HTTP method is not allowed.", 405)
96+
else:
97+
logger.info("Request has no url_rule")
98+
raise ResourceError(
99+
"Invalid request: the API endpoint does not exist or the provided HTTP method is not allowed.", 405)
87100
try:
88101
nonce_id = request.args['x-nonce']
89102
except KeyError:
@@ -273,7 +286,7 @@ def check_privileged():
273286
data = request.get_json()
274287
if not data:
275288
data = request.form
276-
# various APIs (e.g., the state api) allow an arbitary JSON serializable objects which won't have a get method:
289+
# various APIs (e.g., the state api) allow an arbitrary JSON serializable objects which won't have a get method:
277290
if not hasattr(data, 'get'):
278291
return True
279292
if not codes.PRIVILEGED_ROLE in g.roles:
@@ -312,12 +325,16 @@ def check_privileged():
312325
logger.debug("not trying to use privileged options.")
313326
return True
314327

315-
def check_permissions(user, identifier, level):
328+
def check_permissions(user, identifier, level, roles=None):
316329
"""Check the permissions store for user and level. Here, `identifier` is a unique id in the
317330
permissions_store; e.g., actor db_id or alias_id.
318331
"""
319332
logger.debug("Checking user: {} permissions for identifier: {}".format(user, identifier))
320-
# get all permissions for this actor
333+
# first, if roles were passed, check for admin role -
334+
if roles:
335+
if codes.ADMIN_ROLE in roles:
336+
return True
337+
# get all permissions for this actor -
321338
permissions = get_permissions(identifier)
322339
for p_user, p_name in permissions.items():
323340
# if the actor has been shared with the WORLD_USER anyone can use it
@@ -354,7 +371,10 @@ def get_db_id():
354371
logger.error("Unrecognized request -- could not find the actor id. path_split: {}".format(path_split))
355372
raise PermissionsException("Not authorized.")
356373
logger.debug("path_split: {}".format(path_split))
357-
actor_identifier = path_split[idx]
374+
try:
375+
actor_identifier = path_split[idx]
376+
except IndexError:
377+
raise ResourceError("Unable to parse actor identifier: is it missing from the URL?", 404)
358378
logger.debug("actor_identifier: {}; tenant: {}".format(actor_identifier, g.tenant))
359379
try:
360380
actor_id = Actor.get_actor_id(g.tenant, actor_identifier)

actors/codes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def __repr__(self):
6767

6868
PERMISSION_LEVELS = (NONE.name, READ.name, EXECUTE.name, UPDATE.name)
6969

70+
ALIAS_NONCE_PERMISSION_LEVELS = (NONE.name, READ.name, EXECUTE.name)
71+
7072
# role set by agaveflask in case the access_control_type is none
7173
ALL_ROLE = 'ALL'
7274

actors/controllers.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from auth import check_permissions, get_tas_data, tenant_can_use_tas, get_uid_gid_homedir, get_token_default
1717
from channels import ActorMsgChannel, CommandChannel, ExecutionResultsChannel, WorkerChannel
18-
from codes import SUBMITTED, COMPLETE, SHUTTING_DOWN, PERMISSION_LEVELS, READ, UPDATE, EXECUTE, PERMISSION_LEVELS, PermissionLevel
18+
from codes import SUBMITTED, COMPLETE, SHUTTING_DOWN, PERMISSION_LEVELS, ALIAS_NONCE_PERMISSION_LEVELS, READ, UPDATE, EXECUTE, PERMISSION_LEVELS, PermissionLevel
1919
from config import Config
2020
from errors import DAOError, ResourceError, PermissionsException, WorkerException
2121
from models import dict_to_camel, display_time, is_hashid, Actor, Alias, Execution, ExecutionsSummary, Nonce, Worker, get_permissions, \
@@ -138,7 +138,7 @@ def check_metrics(self, actor_ids, inbox_lengths, cmd_length):
138138
except:
139139
hints = []
140140
for hint in hints:
141-
if hint == actor.SYNC_HINT:
141+
if hint == Actor.SYNC_HINT:
142142
is_sync_actor = True
143143
break
144144
metrics_utils.scale_down(actor_id, is_sync_actor)
@@ -333,6 +333,10 @@ def post(self):
333333
logger.debug("did not find actor: {}.".format(dbid))
334334
raise ResourceError(
335335
"No actor found with id: {}.".format(actor_id), 404)
336+
# update 10/2019: check that use has UPDATE permission on the actor -
337+
if not check_permissions(user=g.user, identifier=dbid, level=codes.UPDATE):
338+
raise PermissionsException(f"Not authorized -- you do not have access to {actor_id}.")
339+
336340
# supply "provided" fields:
337341
args['tenant'] = g.tenant
338342
args['db_id'] = dbid
@@ -358,9 +362,66 @@ def get(self, alias):
358362
logger.debug("did not find alias with id: {}".format(alias))
359363
raise ResourceError(
360364
"No alias found: {}.".format(alias), 404)
361-
logger.debug("found actor {}".format(alias))
365+
logger.debug("found alias {}".format(alias))
362366
return ok(result=alias.display(), msg="Alias retrieved successfully.")
363367

368+
def validate_put(self):
369+
logger.debug("top of validate_put")
370+
try:
371+
data = request.get_json()
372+
except:
373+
data = None
374+
if data and 'alias' in data or 'alias' in request.form:
375+
logger.debug("found alias in the PUT.")
376+
raise DAOError("Invalid alias update description. The alias itself cannot be updated in a PUT request.")
377+
parser = Alias.request_parser()
378+
logger.debug("got the alias parser")
379+
# remove since alias is only required for POST, not PUT
380+
parser.remove_argument('alias')
381+
try:
382+
args = parser.parse_args()
383+
except BadRequest as e:
384+
msg = 'Unable to process the JSON description.'
385+
if hasattr(e, 'data'):
386+
msg = e.data.get('message')
387+
raise DAOError("Invalid alias description. Missing required field: {}".format(msg))
388+
return args
389+
390+
def put(self, alias):
391+
logger.debug("top of PUT /actors/aliases/{}".format(alias))
392+
alias_id = Alias.generate_alias_id(g.tenant, alias)
393+
try:
394+
alias_obj = Alias.from_db(alias_store[alias_id])
395+
except KeyError:
396+
logger.debug("did not find alias with id: {}".format(alias))
397+
raise ResourceError("No alias found: {}.".format(alias), 404)
398+
logger.debug("found alias {}".format(alias_obj))
399+
args = self.validate_put()
400+
actor_id = args.get('actor_id')
401+
if Config.get('web', 'case') == 'camel':
402+
actor_id = args.get('actorId')
403+
dbid = Actor.get_dbid(g.tenant, actor_id)
404+
# update 10/2019: check that use has UPDATE permission on the actor -
405+
if not check_permissions(user=g.user, identifier=dbid, level=codes.UPDATE, roles=g.roles):
406+
raise PermissionsException(f"Not authorized -- you do not have UPDATE "
407+
f"access to the actor you want to associate with this alias.")
408+
logger.debug(f"dbid: {dbid}")
409+
# supply "provided" fields:
410+
args['tenant'] = alias_obj.tenant
411+
args['db_id'] = dbid
412+
args['owner'] = alias_obj.owner
413+
args['alias'] = alias_obj.alias
414+
args['alias_id'] = alias_obj.alias_id
415+
args['api_server'] = alias_obj.api_server
416+
logger.debug("Instantiating alias object. args: {}".format(args))
417+
new_alias_obj = Alias(**args)
418+
logger.debug("Alias object instantiated; updating alias in alias_store. "
419+
"alias: {}".format(new_alias_obj))
420+
alias_store[alias_id] = new_alias_obj
421+
logger.info("alias updated for actor: {}.".format(dbid))
422+
set_permission(g.user, new_alias_obj.alias_id, UPDATE)
423+
return ok(result=new_alias_obj.display(), msg="Actor alias updated successfully.")
424+
364425
def delete(self, alias):
365426
logger.debug("top of DELETE /actors/aliases/{}".format(alias))
366427
alias_id = Alias.generate_alias_id(g.tenant, alias)
@@ -370,6 +431,13 @@ def delete(self, alias):
370431
logger.debug("did not find alias with id: {}".format(alias))
371432
raise ResourceError(
372433
"No alias found: {}.".format(alias), 404)
434+
435+
# update 10/2019: check that use has UPDATE permission on the actor -
436+
# TODO - check: do we want to require UPDATE on the actor to delete the alias? Seems like UPDATE
437+
# on the alias iteself should be sufficient...
438+
# if not check_permissions(user=g.user, identifier=alias.db_id, level=codes.UPDATE):
439+
# raise PermissionsException(f"Not authorized -- you do not have UPDATE "
440+
# f"access to the actor associated with this alias.")
373441
try:
374442
del alias_store[alias_id]
375443
# also remove all permissions - there should be at least one permissions associated
@@ -435,10 +503,11 @@ def validate_post(self):
435503
msg = e.data.get('message')
436504
raise DAOError("Invalid nonce description: {}".format(msg))
437505
# additional checks
506+
438507
if 'level' in args:
439-
if not args['level'] in PERMISSION_LEVELS:
508+
if not args['level'] in ALIAS_NONCE_PERMISSION_LEVELS:
440509
raise DAOError("Invalid nonce description. "
441-
"The level attribute must be one of: {}".format(PERMISSION_LEVELS))
510+
"The level attribute must be one of: {}".format(ALIAS_NONCE_PERMISSION_LEVELS))
442511
if Config.get('web', 'case') == 'snake':
443512
if 'max_uses' in args:
444513
self.validate_max_uses(args['max_uses'])
@@ -614,9 +683,11 @@ def get(self):
614683
return ok(result=actors, msg="Actors retrieved successfully.")
615684

616685
def validate_post(self):
686+
logger.debug("top of validate post in /actors")
617687
parser = Actor.request_parser()
618688
try:
619689
args = parser.parse_args()
690+
logger.debug(f"initial actor args from parser: {args}")
620691
if args['queue']:
621692
queues_list = Config.get('spawner', 'host_queues').replace(' ', '')
622693
valid_queues = queues_list.split(',')

actors/metrics_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def scale_down(actor_id, is_sync_actor=False):
179179
if len(workers) == 1 and is_sync_actor:
180180
logger.debug("only one worker, on sync actor. checking worker idle time..")
181181
try:
182-
sync_max_idle_time = int(Config.get('worker', 'sync_max_idle_time'))
182+
sync_max_idle_time = int(Config.get('workers', 'sync_max_idle_time'))
183183
except Exception as e:
184184
logger.error(f"Got exception trying to read sync_max_idle_time from config; e:{e}")
185185
sync_max_idle_time = DEFAULT_SYNC_MAX_IDLE_TIME

actors/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ def set_logs(cls, exc_id, logs):
10191019
max_log_length = DEFAULT_MAX_LOG_LENGTH
10201020
if len(logs) > DEFAULT_MAX_LOG_LENGTH:
10211021
logger.info("truncating log for execution: {}".format(exc_id))
1022-
logs = logs[:max_log_length]
1022+
logs = logs[:max_log_length] + " LOG LIMIT EXCEEDED; this execution log was TRUNCATED!"
10231023
start_timer = timeit.default_timer()
10241024
if log_ex > 0:
10251025
logger.info("Storing log with expiry. exc_id: {}".format(exc_id))

docs/specs/openapi_v3.yml

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,38 @@ paths:
104104
content:
105105
application/json:
106106
schema:
107-
allOf:
108-
- $ref: '#/components/schemas/BasicResponse'
107+
# allOf:
108+
# - $ref: '#/components/schemas/BasicResponse'
109+
properties:
110+
result:
111+
$ref: '#/components/schemas/Actor'
112+
put:
113+
tags:
114+
- Actors
115+
summary: Update an actor
116+
description: Update an actor's definition.
117+
operationId: updateActor
118+
parameters:
119+
- name: actor_id
120+
in: path
121+
description: Unique ID of the actor
122+
required: true
123+
schema:
124+
type: string
125+
requestBody:
126+
required: true
127+
content:
128+
application/json:
129+
schema:
130+
$ref: '#/components/schemas/NewActor'
131+
responses:
132+
200:
133+
description: OK
134+
content:
135+
application/json:
136+
schema:
137+
# allOf:
138+
# - $ref: '#/components/schemas/BasicResponse'
109139
properties:
110140
result:
111141
$ref: '#/components/schemas/Actor'
@@ -745,6 +775,37 @@ paths:
745775
properties:
746776
result:
747777
$ref: '#/components/schemas/Alias'
778+
put:
779+
tags:
780+
- Actors
781+
- Aliases
782+
summary: Update an actor alias
783+
description: Update an alias definition.
784+
operationId: updateActorAlias
785+
parameters:
786+
- name: alias
787+
in: path
788+
description: Unique alias of the actor
789+
required: true
790+
schema:
791+
type: string
792+
requestBody:
793+
required: true
794+
content:
795+
application/json:
796+
schema:
797+
$ref: '#/components/schemas/NewAlias'
798+
responses:
799+
200:
800+
description: OK
801+
content:
802+
application/json:
803+
schema:
804+
allOf:
805+
- $ref: '#/components/schemas/BasicResponse'
806+
properties:
807+
result:
808+
$ref: '#/components/schemas/Alias'
748809
delete:
749810
tags:
750811
- Actors
@@ -898,8 +959,8 @@ components:
898959

899960
Actor:
900961
type: object
901-
allOf:
902-
- $ref: '#/components/schemas/ArrayOfActorMounts'
962+
# allOf:
963+
# - $ref: '#/components/schemas/ArrayOfActorMounts'
903964
properties:
904965
id:
905966
type: string
@@ -922,6 +983,8 @@ components:
922983
link:
923984
type: string
924985
description: Actor identifier of actor to link this actor's events too. May be an actor id or an alias. Cycles not permitted.
986+
mounts:
987+
$ref: '#/components/schemas/ArrayOfActorMounts'
925988
owner:
926989
type: string
927990
description: The user who created this actor.
@@ -1187,4 +1250,3 @@ components:
11871250

11881251

11891252

1190-

local-dev.conf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ dd: unix://var/run/docker.sock
9393
init_count: 1
9494

9595
# set whether autoscaling is enabled
96-
autoscaling = false
96+
autoscaling = true
9797

9898
# max length of time, in seconds, an actor container is allowed to execute before being killed.
9999
# set to -1 for indefinite execution time.
@@ -134,7 +134,7 @@ auto_remove: true
134134
# Whether the workers should have OAuth clients generated for them:
135135
generate_clients: False
136136
# specifiy client generation is available for a specific tenant -
137-
DEV-STAGING_generate_clients: True
137+
# DEV-STAGING_generate_clients: True
138138

139139
# a list of mounts to make for every actor container, separated by comma (,) characters.
140140
# Mounts should be of the form <absolute_host_path>:<absolute_container_path>:<mode>

samples/mem_limit/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Image: abacosamples/mem_limit
2+
from abacosamples/py3_base
3+
4+
ADD actor.py /actor.py
5+
6+
CMD ["python", "/actor.py"]

samples/mem_limit/README.rst

Whitespace-only changes.

0 commit comments

Comments
 (0)