Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
0060ca3
Allow template parameters to be optional
ehlertjd Jan 11, 2018
89363b4
Initial addition of reaper upload API doc
ryansanford Jan 12, 2018
1bc08eb
Add jobs/logs, jobs/prepare-complete, jobs/accept-failed-output endpo…
davidfarkas93 Jan 12, 2018
5478bf6
Document site rules
ambrussimon Jan 12, 2018
aeada8d
Add missing DELETE file
ehlertjd Jan 30, 2018
8b2feef
Reorder tags
ehlertjd Jan 30, 2018
081bbdb
Add schemas for gear APIs.
ehlertjd Jan 30, 2018
18c4b8d
Add schema and endpoint for jobs
ehlertjd Jan 31, 2018
275e0f5
Add docs for modify info
ehlertjd Feb 6, 2018
89de560
Add documentation for PUT on file endpoints
ehlertjd Feb 7, 2018
ea0d39b
Add missing parameter to collection/acquisitions
ehlertjd Feb 7, 2018
58bc7f3
Add missing gear invocation endpoint
ehlertjd Feb 7, 2018
c11464f
Rename update_job endpoint
ehlertjd Feb 9, 2018
1631af8
Add get_analyses endpoint documentation
ehlertjd Feb 9, 2018
1b250ce
Fix failing tests for analyses
ehlertjd Feb 9, 2018
866949f
Add schemas for batch job scheduling
ehlertjd Feb 9, 2018
b20a26a
Add schema for adding job logs
ehlertjd Feb 9, 2018
89cc372
Add API documentation for search endpoint
ehlertjd Feb 9, 2018
06e4c99
Cleanup packfile endpoints
ehlertjd Feb 13, 2018
1135641
Add documentation for resolver endpoint
ehlertjd Mar 2, 2018
7496d0f
Added step to simplify swagger for code generation. Fixed a few missi…
ehlertjd Jan 2, 2018
452f617
Added additional model simplifications
ehlertjd Jan 8, 2018
e3eb466
Alias primitive types in definitions
ehlertjd Jan 11, 2018
5bda420
Added support for conversion from patternProperties to additionalProp…
ehlertjd Jan 11, 2018
11c709c
Replace pure references in definitions with aliases
ehlertjd Jan 11, 2018
786a2aa
Checkpoint commit of modifications for codegen.
ehlertjd Jan 15, 2018
510cc77
Added x-sdk-include-empty for Gear model
ehlertjd Jan 31, 2018
9be94d1
Rename file to file-entry (python codegen had a problem with that name)
ehlertjd Feb 2, 2018
0a47869
API Doc changes for codegen
ehlertjd Feb 5, 2018
3677395
Added date-time format to created/modified timestamps
ehlertjd Feb 5, 2018
68c8c68
Set default values for info_exists.
ehlertjd Feb 6, 2018
fee194e
Fix failing unit tests due to schema changes
ehlertjd Feb 7, 2018
e0bddb9
Update file to file-entry in analysis list
ehlertjd Feb 9, 2018
cb7de2e
Update schemas for analyses in SDK
ehlertjd Feb 9, 2018
e467742
Ignore '+' and '-' properties for download filters
ehlertjd Feb 9, 2018
08bdbd9
Add default values for search parameters
ehlertjd Feb 9, 2018
6086aa0
Update packfile output definition
ehlertjd Feb 13, 2018
4d35b4d
Add model merge for swagger codegen
ehlertjd Feb 27, 2018
087a3b1
Add additional handling for polymorphic models
ehlertjd Mar 2, 2018
49f97eb
Generalize resolver approach
ehlertjd Mar 5, 2018
8c70ad3
Add lookup endpoint that routes to GET for path
ehlertjd Mar 6, 2018
9304596
Move list_projection into ContainerStorage
ehlertjd Mar 6, 2018
0073041
Use ContainerStorage for resolver
ehlertjd Mar 7, 2018
3049c32
Add swagger documentation for lookup endpoint
ehlertjd Mar 8, 2018
22712cb
Fix test failing due to indeterminate ordering
ehlertjd Mar 8, 2018
277deed
Refactor resolver to support virtual nodes
ehlertjd Mar 9, 2018
31f0a5b
Add gears to resolver
ehlertjd Mar 9, 2018
94d2fd6
Add optional query to get_analyses
ehlertjd Mar 9, 2018
234274a
Add analyses to resolver
ehlertjd Mar 9, 2018
4791196
Add resolver definitions for gears and analyses
ehlertjd Mar 12, 2018
21e34fc
Improve resolver test coverage
ehlertjd Mar 12, 2018
7c73798
Rename resolver's node_type to container_type
ehlertjd Mar 12, 2018
230a39e
Resolve review comments
ehlertjd Mar 14, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def prefix(path, routes):

route('/login', RequestHandler, h='log_in', m=['POST']),
route('/logout', RequestHandler, h='log_out', m=['POST']),
route('/lookup', ResolveHandler, h='lookup', m=['POST']),
route('/resolve', ResolveHandler, h='resolve', m=['POST']),
route('/schemas/<schema:{schema}>', SchemaHandler, m=['GET']),
route('/report/<report_type:site|project|accesslog|usage>', ReportHandler, m=['GET']),
Expand Down
12 changes: 9 additions & 3 deletions api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,17 +160,23 @@ def apply_env_variables(config):
'device.json',
'file.json',
'file-update.json',
'gear.json',
'group-new.json',
'group-update.json',
'info_update.json',
'job-logs.json',
'job-new.json',
'note.json',
'packfile.json',
'permission.json',
'project.json',
'project-template.json',
'project-update.json',
'propose-batch.json',
'resolver.json',
'rule-new.json',
'rule-update.json',
'search-query.json',
'session.json',
'session-update.json',
'subject.json',
Expand Down Expand Up @@ -225,11 +231,11 @@ def initialize_db():
log.info('Initializing database, creating indexes')
# TODO review all indexes
db.users.create_index('api_key.key')
db.projects.create_index([('gid', 1), ('name', 1)])
db.sessions.create_index('project')
db.projects.create_index([('group', 1), ('label', 1)])
db.sessions.create_index([('project', 1), ('label', 1)])
db.sessions.create_index('uid')
db.sessions.create_index('created')
db.acquisitions.create_index('session')
db.acquisitions.create_index([('session', 1), ('label', 1)])
db.acquisitions.create_index('uid')
db.acquisitions.create_index('collections')
db.analyses.create_index([('parent.type', 1), ('parent.id', 1)])
Expand Down
39 changes: 33 additions & 6 deletions api/dao/basecontainerstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def get_container(self, _id, projection=None, get_children=False):
cont[CHILD_MAP[self.cont_name]] = children
return cont

def get_child_container_name(self):
return CHILD_MAP.get(self.cont_name)

def get_children(self, _id, projection=None, uid=None):
try:
child_name = CHILD_MAP[self.cont_name]
Expand Down Expand Up @@ -210,11 +213,21 @@ def get_el(self, _id, projection=None, fill_defaults=False):
self._from_mongo(cont)
if fill_defaults:
self._fill_default_values(cont)
if cont is not None and cont.get('files', []):
cont['files'] = [f for f in cont['files'] if 'deleted' not in f]
self.filter_deleted_files(cont)
return cont

def get_all_el(self, query, user, projection, fill_defaults=False):
def get_all_el(self, query, user, projection, fill_defaults=False, **kwargs):
"""
Get all elements matching query for this container.

Args:
query (dict): The query object, or None for all elements
user (dict): The user object, if filtering on permissions is desired, otherwise None
projection (dict): The optional projection to use for returned elements
fill_defaults (bool): Whether or not to populate the default values for returned elements. Default is False.
**kwargs: Additional arguments to pass to the underlying find function

"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

if query is None:
query = {}
if user:
Expand All @@ -238,10 +251,9 @@ def get_all_el(self, query, user, projection, fill_defaults=False):
else:
replace_info_with_bool = False

results = list(self.dbc.find(query, projection))
results = list(self.dbc.find(query, projection, **kwargs))
for cont in results:
if cont.get('files', []):
cont['files'] = [f for f in cont['files'] if 'deleted' not in f]
self.filter_deleted_files(cont)
self._from_mongo(cont)
if fill_defaults:
self._fill_default_values(cont)
Expand Down Expand Up @@ -304,3 +316,18 @@ def modify_info(self, _id, payload, modify_subject=False):
update['$set']['modified'] = datetime.datetime.utcnow()

return self.dbc.update_one(query, update)

def filter_deleted_files(self, cont):
"""
Update container object, removing any files that are marked deleted.
"""
if cont is not None and 'files' in cont:
cont['files'] = [f for f in cont['files'] if 'deleted' not in f]


def get_list_projection(self):
"""
Return a copy of the list projection to use with this container, or None.
It is safe to modify the returned copy.
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm so-so on this function, but I can see why you did it.

return None
32 changes: 27 additions & 5 deletions api/dao/containerstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def create_el(self, payload):


class ProjectStorage(ContainerStorage):

def __init__(self):
super(ProjectStorage,self).__init__('projects', use_object_id=True, use_delete_tag=True)

Expand Down Expand Up @@ -96,6 +95,9 @@ def recalc_sessions_compliance(self, project_id=None):
changed_sessions.append(s['_id'])
return changed_sessions

def get_list_projection(self):
return {'info': 0, 'files.info': 0}


class SessionStorage(ContainerStorage):

Expand Down Expand Up @@ -220,6 +222,14 @@ def get_all_for_targets(self, target_type, target_ids, user=None, projection=Non

return self.get_all_el(query, user, projection)

def get_list_projection(self):
# Remove subject first/last from list view to better log access to this information
return {'info': 0, 'analyses': 0, 'subject.firstname': 0,
'subject.lastname': 0, 'subject.sex': 0, 'subject.age': 0,
'subject.race': 0, 'subject.ethnicity': 0, 'subject.info': 0,
'files.info': 0, 'tags': 0}




class AcquisitionStorage(ContainerStorage):
Expand Down Expand Up @@ -287,12 +297,18 @@ def get_all_for_targets(self, target_type, target_ids, user=None, projection=Non
query['collections'] = collection_id
return self.get_all_el(query, user, projection)

def get_list_projection(self):
return {'info': 0, 'collections': 0, 'files.info': 0, 'tags': 0}


class CollectionStorage(ContainerStorage):

def __init__(self):
super(CollectionStorage, self).__init__('collections', use_object_id=True, use_delete_tag=True)

def get_list_projection(self):
return {'info': 0}


class AnalysisStorage(ContainerStorage):

Expand All @@ -305,10 +321,13 @@ def get_parent(self, parent_type, parent_id):
return parent_storage.get_container(parent_id)


def get_analyses(self, parent_type, parent_id, inflate_job_info=False):
parent_type = containerutil.singularize(parent_type)
parent_id = bson.ObjectId(parent_id)
analyses = self.get_all_el({'parent.type': parent_type, 'parent.id': parent_id}, None, None)
def get_analyses(self, query, parent_type, parent_id, inflate_job_info=False, projection=None, **kwargs):
if query is None:
query = {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing as query is a required param, I would advocate for not tolerating None here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContainerStorage.get_all_ell allows None for query, so I was following that model.

query['parent.type'] = containerutil.singularize(parent_type)
query['parent.id'] = bson.ObjectId(parent_id)

analyses = self.get_all_el(query, None, projection, **kwargs)
if inflate_job_info:
for analysis in analyses:
self.inflate_job_info(analysis)
Expand Down Expand Up @@ -410,3 +429,6 @@ def inflate_job_info(self, analysis):

analysis['job'] = job
return analysis

def get_list_projection(self):
return {'info': 0, 'files.info': 0, 'tags': 0}
16 changes: 10 additions & 6 deletions api/handlers/collectionshandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ class CollectionsHandler(ContainerHandler):

container_handler_configurations['collections'] = {
'permchecker': containerauth.collection_permissions,
'storage': containerstorage.ContainerStorage('collections', use_object_id=True, use_delete_tag=True),
'storage': containerstorage.CollectionStorage(),
'storage_schema_file': 'collection.json',
'payload_schema_file': 'collection.json',
'list_projection': {'info': 0}
'payload_schema_file': 'collection.json'
}

def __init__(self, request=None, response=None):
Expand Down Expand Up @@ -116,7 +115,7 @@ def delete(self, **kwargs):
self.abort(404, 'Element not removed from container {} {}'.format(self.storage.cont_name, _id))

def get_all(self):
projection = self.container_handler_configurations['collections']['list_projection']
projection = self.get_list_projection('collections')
if self.superuser_request:
permchecker = always_ok
elif self.public_request:
Expand Down Expand Up @@ -163,7 +162,7 @@ def get_sessions(self, cid):
if not self.superuser_request:
query['permissions._id'] = self.uid

projection = self.container_handler_configurations['sessions']['list_projection']
projection = self.get_list_projection('sessions')

sessions = list(containerstorage.SessionStorage().get_all_el(query, None, projection))

Expand Down Expand Up @@ -193,7 +192,7 @@ def get_acquisitions(self, cid):
if not self.superuser_request:
query['permissions._id'] = self.uid

projection = self.container_handler_configurations['acquisitions']['list_projection']
projection = self.get_list_projection('acquisitions')

acquisitions = list(containerstorage.AcquisitionStorage().get_all_el(query, None, projection))

Expand All @@ -202,3 +201,8 @@ def get_acquisitions(self, cid):
for acquisition in acquisitions:
acquisition = self.handle_origin(acquisition)
return acquisitions

def get_list_projection(self, container):
"""Return the list_projection for container."""
cfg = self.container_handler_configurations[container]
return cfg['storage'].get_list_projection()
21 changes: 8 additions & 13 deletions api/handlers/containerhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ class ContainerHandler(base.RequestHandler):
'sessions': True,
'acquisitions': True
}
default_list_projection = ['files', 'notes', 'timestamp', 'timezone', 'public']

# This configurations are used by the ContainerHandler class to load the storage,
# the permissions checker and the json schema validators used to handle a request.
Expand All @@ -57,7 +56,6 @@ class ContainerHandler(base.RequestHandler):
'parent_storage': containerstorage.GroupStorage(),
'storage_schema_file': 'project.json',
'payload_schema_file': 'project.json',
'list_projection': {'info': 0, 'files.info': 0},
'propagated_properties': ['public'],
'children_cont': 'sessions'
},
Expand All @@ -67,20 +65,14 @@ class ContainerHandler(base.RequestHandler):
'parent_storage': containerstorage.ProjectStorage(),
'storage_schema_file': 'session.json',
'payload_schema_file': 'session.json',
# Remove subject first/last from list view to better log access to this information
'list_projection': {'info': 0, 'analyses': 0, 'subject.firstname': 0,
'subject.lastname': 0, 'subject.sex': 0, 'subject.age': 0,
'subject.race': 0, 'subject.ethnicity': 0, 'subject.info': 0,
'files.info': 0, 'tags': 0},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 to this moving out of this file

'children_cont': 'acquisitions'
},
'acquisitions': {
'storage': containerstorage.AcquisitionStorage(),
'permchecker': containerauth.default_container,
'parent_storage': containerstorage.SessionStorage(),
'storage_schema_file': 'acquisition.json',
'payload_schema_file': 'acquisition.json',
'list_projection': {'info': 0, 'collections': 0, 'files.info': 0, 'tags': 0}
'payload_schema_file': 'acquisition.json'
}
}

Expand Down Expand Up @@ -114,7 +106,10 @@ def get(self, cont_name, **kwargs):
fileinfo['path'] = util.path_from_hash(fileinfo['hash'])

inflate_job_info = cont_name == 'sessions'
result['analyses'] = AnalysisStorage().get_analyses(cont_name, _id, inflate_job_info)
result['analyses'] = AnalysisStorage().get_analyses(None, cont_name, _id, inflate_job_info)

util.add_container_type(self.request, result)

return self.handle_origin(result)

def handle_origin(self, result):
Expand Down Expand Up @@ -240,7 +235,7 @@ def get_jobs(self, cid):

permchecker(noop)('GET', cid)

analyses = AnalysisStorage().get_analyses('session', cont['_id'])
analyses = AnalysisStorage().get_analyses(None, 'session', cont['_id'])
acquisitions = cont.get('acquisitions', [])

results = []
Expand Down Expand Up @@ -311,7 +306,7 @@ def get_all(self, cont_name, par_cont_name=None, par_id=None):
self.config = self.container_handler_configurations[cont_name]
self.storage = self.config['storage']

projection = self.config['list_projection'].copy()
projection = self.storage.get_list_projection()

if self.is_true('permissions'):
if not projection:
Expand Down Expand Up @@ -384,7 +379,7 @@ def _add_results_counts(self, results, cont_name):
def get_all_for_user(self, cont_name, uid):
self.config = self.container_handler_configurations[cont_name]
self.storage = self.config['storage']
projection = self.config['list_projection']
projection = self.storage.get_list_projection()
# select which permission filter will be applied to the list of results.
if self.superuser_request or self.user_is_admin:
permchecker = always_ok
Expand Down
1 change: 1 addition & 0 deletions api/handlers/grouphandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def get(self, _id):
self._filter_permissions([result], self.uid)
if self.is_true('join_avatars'):
ContainerHandler.join_user_info([result])
util.add_container_type(self.request, result)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This & its brethren are adding that extra key onto each single-container GET, correct?
So that key will start showing up pretty much everywhere except list endpoints?

If so, good, just making sure I understand correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will only show up if you set the fw_container_type key in the environment, so currently it's only for forwarded requests. I wouldn't have any complaints about that key showing up everywhere, so I'm open to that conversation; if that were the case I'd advocate moving the logic into ContainerStorage.

return result

def delete(self, _id):
Expand Down
2 changes: 2 additions & 0 deletions api/handlers/refererhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ def get(self, **kwargs):
if self.is_true('inflate_job'):
self.storage.inflate_job_info(analysis)

util.add_container_type(self.request, analysis)

self.log_user_access(AccessType.view_container, cont_name=analysis['parent']['type'], cont_id=analysis['parent']['id'])
return analysis

Expand Down
Loading