Skip to content

Commit f12f91e

Browse files
committed
Implementation of Code Review requests. New format for override key. Strip validate_application_header_before_entity_create from all before_entity_update_validator entries. Comments added for clarity. Consolidate entity validator args to one dict. Remove block on modifying public Collections from previous Issue, so now blocked by new validator and block can be overridden.
1 parent 05b958e commit f12f91e

File tree

5 files changed

+35
-34
lines changed

5 files changed

+35
-34
lines changed

src/app.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,6 @@
9393
# updates to entities with characteristics prohibiting their modification.
9494
LOCKED_ENTITY_UPDATE_OVERRIDE_KEY = app.config['LOCKED_ENTITY_UPDATE_OVERRIDE_KEY']
9595

96-
# Compile the only accept regular expression pattern for the lockout override key in an HTTP Request Header.
97-
# N.B. This is case-insensitive matching "Override-Key", but case-sensitive matching the value.
98-
LOCKED_ENTITY_UPDATE_OVERRIDE_PATTERN = rf"^\s*(?i:override-key)\s+{re.escape(LOCKED_ENTITY_UPDATE_OVERRIDE_KEY)}\s*$"
99-
LOCKED_ENTITY_UPDATE_OVERRIDE_REGEX = re.compile(pattern=LOCKED_ENTITY_UPDATE_OVERRIDE_PATTERN)
100-
10196
# Suppress InsecureRequestWarning warning when requesting status on https with ssl cert verify disabled
10297
requests.packages.urllib3.disable_warnings(category = InsecureRequestWarning)
10398

@@ -1372,9 +1367,6 @@ def update_entity(id):
13721367
# Normalize user provided entity_type
13731368
normalized_entity_type = schema_manager.normalize_entity_type(entity_dict['entity_type'])
13741369

1375-
# Note, we don't support entity level validators on entity update via PUT
1376-
# Only entity create via POST is supported at the entity level
1377-
# KBKBKB...or do we?
13781370
# Execute entity level validator defined in schema yaml before entity modification.
13791371
lockout_overridden = False
13801372
try:
@@ -1389,13 +1381,15 @@ def update_entity(id):
13891381
except schema_errors.LockedEntityUpdateException as leue:
13901382
# HTTP header names are case-insensitive, and request.headers.get() returns None if the header doesn't exist
13911383
locked_entity_update_header = request.headers.get(SchemaConstants.LOCKED_ENTITY_UPDATE_HEADER)
1392-
if locked_entity_update_header and bool(LOCKED_ENTITY_UPDATE_OVERRIDE_REGEX.match(locked_entity_update_header)):
1384+
if locked_entity_update_header and (LOCKED_ENTITY_UPDATE_OVERRIDE_KEY == locked_entity_update_header):
13931385
lockout_overridden = True
13941386
logger.info(f"For {entity_dict['entity_type']} {entity_dict['uuid']}"
13951387
f" update prohibited due to {str(leue)},"
13961388
f" but being overridden by valid {SchemaConstants.LOCKED_ENTITY_UPDATE_HEADER} in request.")
13971389
else:
13981390
forbidden_error(leue)
1391+
except Exception as e:
1392+
internal_server_error(e)
13991393

14001394
# Validate request json against the yaml schema
14011395
# Pass in the entity_dict for missing required key check, this is different from creating new entity
@@ -1414,6 +1408,9 @@ def update_entity(id):
14141408
ValueError) as e:
14151409
bad_request_error(e)
14161410

1411+
# Proceed with per-entity updates after passing any entity-level or property-level validations which
1412+
# would have locked out updates.
1413+
#
14171414
# Sample, Dataset, and Upload: additional validation, update entity, after_update_trigger
14181415
# Collection and Donor: update entity
14191416
if normalized_entity_type == 'Sample':
@@ -1498,13 +1495,6 @@ def update_entity(id):
14981495
if has_dataset_uuids_to_link or has_dataset_uuids_to_unlink or has_updated_status:
14991496
after_update(normalized_entity_type, user_token, merged_updated_dict)
15001497
elif schema_manager.entity_type_instanceof(normalized_entity_type, 'Collection'):
1501-
entity_visibility = _get_entity_visibility( normalized_entity_type=normalized_entity_type
1502-
,entity_dict=entity_dict)
1503-
# Prohibit update of an existing Collection if it meets criteria of being visible to public e.g. has DOI.
1504-
if entity_visibility == DataVisibilityEnum.PUBLIC:
1505-
logger.info(f"Attempt to update {normalized_entity_type} with id={id} which has visibility {entity_visibility}.")
1506-
bad_request_error(f"Cannot update {normalized_entity_type} due '{entity_visibility.value}' visibility.")
1507-
15081498
# Generate 'before_update_trigger' data and update the entity details in Neo4j
15091499
merged_updated_dict = update_entity_details(request, normalized_entity_type, user_token, json_data_dict, entity_dict)
15101500

src/instance/app.cfg.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ NEO4J_PASSWORD = '123'
2828

2929
# Secret value presented with the request header value named by
3030
# SchemaConstants.LOCKED_ENTITY_UPDATE_HEADER, expected to be off the form
31-
# X-HuBMAP-Update-Override: Override-Key <LOCKED_ENTITY_UPDATE_OVERRIDE_KEY value which follows>
31+
# X-HuBMAP-Update-Override: <LOCKED_ENTITY_UPDATE_OVERRIDE_KEY value which follows>
3232
LOCKED_ENTITY_UPDATE_OVERRIDE_KEY = 'set during deployment'
3333

3434
# Set MEMCACHED_MODE to False to disable the caching for local development

src/schema/provenance_schema.yaml

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,6 @@ ENTITIES:
194194
############################################# Collection #############################################
195195
Collection:
196196
before_entity_update_validator:
197-
# Only allowed applications can modify an existing Collection via PUT # KBKBKB @TODO confirm introducing this
198-
- validate_application_header_before_entity_create
199197
# Halt modification of entities which are "locked", such as a Dataset with status == 'Published'
200198
- validate_entity_not_locked_before_update
201199
excluded_properties_from_public_response:
@@ -311,8 +309,6 @@ ENTITIES:
311309
# Only allowed applications can create new Dataset via POST
312310
before_entity_create_validator: validate_application_header_before_entity_create
313311
before_entity_update_validator:
314-
# Only allowed applications can modify an existing Dataset via PUT
315-
- validate_application_header_before_entity_create
316312
# Halt modification of entities which are "locked", such as a Dataset with status == 'Published'
317313
- validate_entity_not_locked_before_update
318314
# Dataset can be either derivation source or target
@@ -671,8 +667,6 @@ ENTITIES:
671667
# Only allowed applications can create new Publication via POST
672668
before_entity_create_validator: validate_application_header_before_entity_create
673669
before_entity_update_validator:
674-
# Only allowed applications can modify an existing Collection via PUT # KBKBKB @TODO confirm introducing this
675-
- validate_application_header_before_entity_create
676670
# Halt modification of entities which are "locked", such as a Dataset with status == 'Published'
677671
- validate_entity_not_locked_before_update
678672
# Publications can be either derivation source or target
@@ -780,8 +774,6 @@ ENTITIES:
780774
- submission_id
781775
- label
782776
before_entity_update_validator:
783-
# Only allowed applications can modify an existing Collection via PUT # KBKBKB @TODO confirm introducing this
784-
- validate_application_header_before_entity_create
785777
# Halt modification of entities which are "locked", such as a Dataset with status == 'Published'
786778
- validate_entity_not_locked_before_update
787779
properties:
@@ -918,8 +910,6 @@ ENTITIES:
918910
# Both Sample and Donor ancestors of a Sample must have these fields removed
919911
- submission_id
920912
before_entity_update_validator:
921-
# Only allowed applications can modify an existing Collection via PUT # KBKBKB @TODO confirm introducing this
922-
- validate_application_header_before_entity_create
923913
# Halt modification of entities which are "locked", such as a Dataset with status == 'Published'
924914
- validate_entity_not_locked_before_update
925915
properties:
@@ -1146,7 +1136,10 @@ ENTITIES:
11461136
Upload:
11471137
# Only allowed applications can create new Upload via POST
11481138
before_entity_create_validator: validate_application_header_before_entity_create
1149-
# Upload requires an ancestor of Lab, and and has no allowed decesndents
1139+
# No before_entity_update_validator needed for Upload because the entity is
1140+
# always considered "non-public", and therefore not blocked from update/PUT.
1141+
#
1142+
# Upload requires a Lab entity as an ancestor, and has no allowed descendants
11501143
derivation:
11511144
source: false
11521145
target: false # Set to false since the schema doesn't handle Lab currently
@@ -1295,8 +1288,6 @@ ENTITIES:
12951288
source: false
12961289
target: false
12971290
before_entity_update_validator:
1298-
# Only allowed applications can modify an existing Collection via PUT # KBKBKB @TODO confirm introducing this
1299-
- validate_application_header_before_entity_create
13001291
# Halt modification of entities which are "locked", such as a Dataset with status == 'Published'
13011292
- validate_entity_not_locked_before_update
13021293
properties:

src/schema/schema_manager.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,8 @@ def validate_json_data_against_schema(json_data_dict, normalized_entity_type, ex
11711171
One of the normalized entity types defined in the schema yaml: Donor, Sample, Dataset, Upload, Upload, Publication
11721172
request: Flask request object
11731173
The instance of Flask request passed in from application request
1174+
existing_entity_dict : dict
1175+
The dictionary for an entity, retrieved from Neo4j, for use during update/PUT validations
11741176
"""
11751177
def execute_entity_level_validator(validator_type, normalized_entity_type, request, existing_entity_dict=None):
11761178
global _schema
@@ -1196,12 +1198,17 @@ def execute_entity_level_validator(validator_type, normalized_entity_type, reque
11961198

11971199
logger.info(f"To run {validator_type}: {validator_method_name} defined for entity {normalized_entity_type}")
11981200

1201+
# Create a dictionary to hold data need by any entity validator, which must be populated
1202+
# with validator specific requirements when the method to be called is determined.
1203+
options_dict = {}
11991204
if existing_entity_dict is None:
12001205
# Execute the entity-level validation for create/POST
1201-
validator_method_to_call(normalized_entity_type, request)
1206+
options_dict['http_request'] = request
1207+
validator_method_to_call(options_dict)
12021208
else:
12031209
# Execute the entity-level validation for update/PUT
1204-
validator_method_to_call(normalized_entity_type, request, existing_entity_dict)
1210+
options_dict['existing_entity_dict']= existing_entity_dict
1211+
validator_method_to_call(options_dict)
12051212
except schema_errors.MissingApplicationHeaderException as e:
12061213
raise schema_errors.MissingApplicationHeaderException(e)
12071214
except schema_errors.InvalidApplicationHeaderException as e:
@@ -1212,6 +1219,7 @@ def execute_entity_level_validator(validator_type, normalized_entity_type, reque
12121219
msg = f"Failed to call the {validator_type} method: {validator_method_name} defined for entity {normalized_entity_type}"
12131220
# Log the full stack trace, prepend a line with our message
12141221
logger.exception(msg)
1222+
raise e
12151223

12161224

12171225
"""

src/schema/schema_validators.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@
2929
request: Flask request
3030
The instance of Flask request passed in from application request
3131
"""
32-
def validate_application_header_before_entity_create(normalized_entity_type, request, existing_entity_id):
32+
def validate_application_header_before_entity_create(options_dict):
33+
if 'http_request' in options_dict:
34+
request = options_dict['http_request']
35+
else:
36+
logger.error(f"validate_application_header_before_entity_create() expected 'http_request' in"
37+
f" options_dict, but it was missing in {str(options_dict)}.")
38+
raise KeyError("Entity validator internal misconfiguration.")
3339
# A list of applications allowed to create this new entity or update Dataset and Upload
3440
# Use lowercase for comparison
3541
applications_allowed = [
@@ -50,7 +56,13 @@ def validate_application_header_before_entity_create(normalized_entity_type, req
5056
request: Flask request
5157
The instance of Flask request passed in from application request
5258
"""
53-
def validate_entity_not_locked_before_update(normalized_entity_type, request, existing_entity_dict):
59+
def validate_entity_not_locked_before_update(options_dict):
60+
if 'existing_entity_dict' in options_dict:
61+
existing_entity_dict = options_dict['existing_entity_dict']
62+
else:
63+
logger.error(f"validate_entity_not_locked_before_update() expected 'existing_entity_dict' in"
64+
f" options_dict, but it was missing in {str(options_dict)}.")
65+
raise KeyError("Entity validator internal misconfiguration.")
5466
_is_entity_locked_against_update(existing_entity_dict)
5567

5668
##############################################################################################

0 commit comments

Comments
 (0)