Skip to content
Snippets Groups Projects
Commit 26e55fe6 authored by Megan Henning's avatar Megan Henning
Browse files

Merge pull request #271 from scitran/replace-metadata

Add ability to replace metadata fields in API
parents 3d18253e 96b64eb5
No related branches found
No related tags found
No related merge requests found
......@@ -15,7 +15,7 @@ def default_container(handler, container=None, target_parent_container=None):
on the container before actually executing this method.
"""
def g(exec_op):
def f(method, _id=None, payload=None, recursive=False):
def f(method, _id=None, payload=None, recursive=False, r_payload=None, replace_metadata=False):
projection = None
if method == 'GET' and container.get('public', False):
has_access = True
......@@ -48,8 +48,10 @@ def default_container(handler, container=None, target_parent_container=None):
if has_access and projection:
return exec_op(method, _id=_id, payload=payload, projection=projection)
if has_access and recursive:
return exec_op(method, _id=_id, payload=payload, recursive=recursive, r_payload=r_payload, replace_metadata=replace_metadata)
elif has_access:
return exec_op(method, _id=_id, payload=payload)
return exec_op(method, _id=_id, payload=payload, replace_metadata=replace_metadata)
else:
handler.abort(403, 'user not authorized to perform a {} operation on the container'.format(method))
return f
......
......@@ -25,11 +25,13 @@ class ContainerStorage(object):
return self._get_el(_id)
def exec_op(self, action, _id=None, payload=None, query=None, user=None,
public=False, projection=None, recursive=False):
public=False, projection=None, recursive=False, r_payload=None,
replace_metadata=False):
"""
Generic method to exec an operation.
The request is dispatched to the corresponding private methods.
"""
check = consistencychecker.get_container_storage_checker(action, self.cont_name)
data_op = payload or {'_id': _id}
check(data_op)
......@@ -40,7 +42,7 @@ class ContainerStorage(object):
if action == 'DELETE':
return self._delete_el(_id)
if action == 'PUT':
return self._update_el(_id, payload, recursive)
return self._update_el(_id, payload, recursive, r_payload, replace_metadata)
if action == 'POST':
return self._create_el(payload)
raise ValueError('action should be one of GET, POST, PUT, DELETE')
......@@ -49,17 +51,28 @@ class ContainerStorage(object):
log.debug(payload)
return self.dbc.insert_one(payload)
def _update_el(self, _id, payload, recursive=False):
def _update_el(self, _id, payload, recursive=False, r_payload=None, replace_metadata=False):
replace = None
if replace_metadata:
replace = {}
if payload.get('metadata') is not None:
replace['metadata'] = util.mongo_sanitize_fields(payload.pop('metadata'))
if payload.get('subject') is not None and payload['subject'].get('metadata') is not None:
replace['subject.metadata'] = util.mongo_sanitize_fields(payload['subject'].pop('metadata'))
update = {
'$set': util.mongo_dict(payload)
}
if replace is not None:
update['$set'].update(replace)
if self.use_object_id:
try:
_id = bson.objectid.ObjectId(_id)
except bson.errors.InvalidId as e:
raise APIStorageException(e.message)
if recursive:
self._propagate_changes(_id, update)
if recursive and r_payload is not None:
self._propagate_changes(_id, {'$set': util.mongo_dict(r_payload)})
return self.dbc.update_one({'_id': _id}, update)
def _propagate_changes(self, _id, update):
......
......@@ -305,13 +305,16 @@ class ContainerHandler(base.RequestHandler):
payload = self.request.json_body
payload_validator(payload, 'PUT')
# Check if any payload keys are a propogated property, ensure they all are
# Check if any payload keys are any propogated property, add to r_payload
rec = False
if set(payload.keys()).intersection(set(self.config.get('propagated_properties', []))):
if not set(payload.keys()).issubset(set(self.config['propagated_properties'])):
self.abort(400, 'Cannot update propagated properties together with unpropagated properties')
else:
rec = True # Mark PUT request for propogation
r_payload = {}
prop_keys = set(payload.keys()).intersection(set(self.config.get('propagated_properties', [])))
if prop_keys:
rec = True
for key in prop_keys:
r_payload[key] = payload[key]
# Check if we are updating the parent container of the element (ie we are moving the container)
# In this case, we will check permissions on it.
target_parent_container, parent_id_property = self._get_parent_container(payload)
......@@ -331,10 +334,14 @@ class ContainerHandler(base.RequestHandler):
# Ensure subject id is a bson object
payload['subject']['_id'] = bson.ObjectId(str(payload['subject']['_id']))
permchecker = self._get_permchecker(container, target_parent_container)
# Specifies wether the metadata fields should be replaced or patched with payload value
replace_metadata = self.get_param('replace_metadata', default=False)
try:
# This line exec the actual request validating the payload that will update the container
# and checking permissions using respectively the two decorators, mongo_validator and permchecker
result = mongo_validator(permchecker(self.storage.exec_op))('PUT', _id=_id, payload=payload, recursive=rec)
result = mongo_validator(permchecker(self.storage.exec_op))('PUT',
_id=_id, payload=payload, recursive=rec, r_payload=r_payload, replace_metadata=replace_metadata)
except APIStorageException as e:
self.abort(400, e.message)
......
......@@ -48,6 +48,26 @@ def mongo_dict(d):
)
return dict(_mongo_list(d))
def mongo_sanitize_fields(d):
"""
Sanitize keys of arbitrarily structured map without flattening into dot notation
Adapted from http://stackoverflow.com/questions/8429318/how-to-use-dot-in-field-name
"""
if isinstance(d, dict):
return {mongo_sanitize_fields(str(key)): value if isinstance(value, str) else mongo_sanitize_fields(value) for key,value in d.iteritems()}
elif isinstance(d, list):
return [mongo_sanitize_fields(element) for element in i]
elif isinstance(d, str):
# not allowing dots nor dollar signs in fieldnames
d = d.replace('.','_')
d = d.replace('$', '-')
return d
else:
return d
def user_perm(permissions, _id, site=None):
for perm in permissions:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment