Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions api/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ def _get_access(uid, container):
def has_access(uid, container, perm):
return _get_access(uid, container) >= INTEGER_PERMISSIONS[perm]

# Returns true if user has phi access
def check_phi(uid, container):
permissions_list = container.get('permissions', [])
for perm in permissions_list:
if perm['_id'] == uid and perm.get('phi-access'):
return has_access(uid, container, 'ro')
return False

def always_ok(exec_op):
"""
Expand Down
51 changes: 38 additions & 13 deletions api/auth/containerauth.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
Purpose of this module is to define all the permissions checker decorators for the ContainerHandler classes.
"""

from . import _get_access, INTEGER_PERMISSIONS
from copy import deepcopy
from . import _get_access, check_phi, INTEGER_PERMISSIONS
from .. import config
log = config.log


def default_container(handler, container=None, target_parent_container=None):
Expand All @@ -12,8 +14,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, unset_payload=None, recursive=False, r_payload=None, replace_metadata=False):
projection = None
def f(method, _id=None, payload=None, unset_payload=None, recursive=False, r_payload=None, projection=None, replace_metadata=False, phi=False):
additional_error_msg = None
if method == 'GET' and container.get('public', False):
has_access = True
Expand Down Expand Up @@ -46,6 +47,10 @@ def f(method, _id=None, payload=None, unset_payload=None, recursive=False, r_pay
else:
has_access = False

if method == 'GET' and phi:
if not check_phi(handler.uid, container):
handler.abort(403, "User not authorized to view PHI fields on the container.")

if has_access:
return exec_op(method, _id=_id, payload=payload, unset_payload=unset_payload, recursive=recursive, r_payload=r_payload, replace_metadata=replace_metadata, projection=projection)
else:
Expand All @@ -63,7 +68,7 @@ def collection_permissions(handler, container=None, _=None):
Permissions are checked on the collection itself or not at all if the collection is new.
"""
def g(exec_op):
def f(method, _id=None, payload = None):
def f(method, _id=None, payload = None, projection=None, phi=False):
if method == 'GET' and container.get('public', False):
has_access = True
elif method == 'GET':
Expand All @@ -77,8 +82,12 @@ def f(method, _id=None, payload = None):
else:
has_access = False

if method == 'GET' and phi:
if not check_phi(handler.uid, container):
handler.abort(403, "User not authorized to view PHI fields.")

if has_access:
return exec_op(method, _id=_id, payload=payload)
return exec_op(method, _id=_id, payload=payload, projection=projection)
else:
handler.abort(403, 'user not authorized to perform a {} operation on the container'.format(method))
return f
Expand All @@ -87,7 +96,7 @@ def f(method, _id=None, payload = None):

def default_referer(handler, parent_container=None):
def g(exec_op):
def f(method, _id=None, payload=None):
def f(method, _id=None, payload=None, projection = None, phi=False):
access = _get_access(handler.uid, parent_container)
if method == 'GET' and parent_container.get('public', False):
has_access = True
Expand All @@ -96,10 +105,14 @@ def f(method, _id=None, payload=None):
elif method in ['POST', 'PUT', 'DELETE']:
has_access = access >= INTEGER_PERMISSIONS['rw']
else:
has_access = False
has_access = False

if method == 'GET' and phi:
if not check_phi(handler.uid, parent_container):
handler.abort(403, "User not authorized to view PHI fields.")

if has_access:
return exec_op(method, _id=_id, payload=payload)
return exec_op(method, _id=_id, payload=payload, projection=projection)
else:
handler.abort(403, 'user not authorized to perform a {} operation on parent container'.format(method))
return f
Expand All @@ -111,30 +124,42 @@ def public_request(handler, container=None):
For public requests we allow only GET operations on containers marked as public.
"""
def g(exec_op):
def f(method, _id=None, payload = None):
def f(method, _id=None, payload = None, phi=False, projection=None):
if phi:
handler.abort(403, "Must be logged in to view PHI fields.")
if method == 'GET' and container.get('public', False):
return exec_op(method, _id, payload)
return exec_op(method, _id, payload=payload, projection=projection)
else:
handler.abort(403, 'not authorized to perform a {} operation on this container'.format(method))
return f
return g

def list_permission_checker(handler):
def g(exec_op):
def f(method, query=None, user=None, public=False, projection=None):
def f(method, query=None, user=None, public=False, projection=None, phi=False):
if user and (user['_id'] != handler.uid):
handler.abort(403, 'User ' + handler.uid + ' may not see the Projects of User ' + user['_id'])
query['permissions'] = {'$elemMatch': {'_id': handler.uid}}
if handler.is_true('public'):
query['$or'] = [{'public': True}, {'permissions': query.pop('permissions')}]
if phi:
temp_query = deepcopy(query)
temp_query['permissions'] = {'$elemMatch': {'_id': handler.uid, 'phi-access': False}}
log.debug(temp_query)
log.debug(query)
not_allowed = exec_op(method, query=temp_query, user=user, public=public, projection=projection)
if not_allowed:
handler.abort(403, "User does not have PHI access to one or more elements")

return exec_op(method, query=query, user=user, public=public, projection=projection)
return f
return g


def list_public_request(exec_op):
def f(method, query=None, user=None, public=False, projection=None):
def f(method, query=None, user=None, public=False, projection=None, phi=False): # pylint: disable=unused-argument
if public:
query['public'] = True
return exec_op(method, query=query, user=user, public=public, projection=projection)
return f

3 changes: 1 addition & 2 deletions api/dao/basecontainerstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,10 @@ def _to_mongo(self, payload):

def exec_op(self, action, _id=None, payload=None, query=None, user=None,
public=False, projection=None, recursive=False, r_payload=None, # pylint: disable=unused-argument
replace_metadata=False, unset_payload=None):
replace_metadata=False, unset_payload=None, phi=False): # pylint: disable=unused-argument
"""
Generic method to exec a CRUD operation from a REST verb.
"""

check = consistencychecker.get_container_storage_checker(action, self.cont_name)
data_op = payload or {'_id': _id}
check(data_op)
Expand Down
4 changes: 2 additions & 2 deletions api/dao/containerstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,10 @@ 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):
def get_analyses(self, parent_type, parent_id, inflate_job_info=False, projection=None):
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)
analyses = self.get_all_el({'parent.type': parent_type, 'parent.id': parent_id}, None, projection)
if inflate_job_info:
for analysis in analyses:
self.inflate_job_info(analysis)
Expand Down
53 changes: 42 additions & 11 deletions api/handlers/collectionshandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ..dao import containerstorage, containerutil, noop
from ..web.errors import APIStorageException
from ..validators import verify_payload_exists
from ..web.request import AccessType

from .containerhandler import ContainerHandler

Expand All @@ -31,7 +32,7 @@ def __init__(self, request=None, response=None):
self.storage = self.container_handler_configurations['collections']['storage']

def get(self, **kwargs):
return super(CollectionsHandler, self).get('collections', **kwargs)
return super(CollectionsHandler, self).get(cont_name='collections', **kwargs)

def post(self):
mongo_validator, payload_validator = self._get_validators()
Expand All @@ -40,7 +41,8 @@ def post(self):
payload_validator(payload, 'POST')
payload['permissions'] = [{
'_id': self.uid,
'access': 'admin'
'access': 'admin',
'phi-access': True
}]
payload['curator'] = self.uid
payload['created'] = payload['modified'] = datetime.datetime.utcnow()
Expand Down Expand Up @@ -101,22 +103,31 @@ def delete(self, **kwargs):
config.db.acquisitions.update_many({'collections': bson.ObjectId(_id)}, {'$pull': {'collections': bson.ObjectId(_id)}})

def get_all(self):
projection = self.container_handler_configurations['collections']['list_projection']
projection = None
if self.superuser_request:
permchecker = always_ok
elif self.public_request:
permchecker = containerauth.list_public_request
else:
permchecker = containerauth.list_permission_checker(self)
query = {}
results = permchecker(self.storage.exec_op)('GET', query=query, public=self.public_request, projection=projection)
if self.is_true('phi'):
projection = None
phi = True
else:
phi = False
projection = {'info': 0, 'tags': 0, 'files.info':0}
results = permchecker(self.storage.exec_op)('GET', query=query, public=self.public_request, projection=projection, phi=phi)
log.debug(results)
if not self.superuser_request and not self.is_true('join_avatars'):
self._filter_all_permissions(results, self.uid)
if self.is_true('join_avatars'):
results = ContainerHandler.join_user_info(results)
for result in results:
if self.is_true('stats'):
result = containerutil.get_stats(result, 'collections')
if phi:
self.log_user_access(AccessType.view_container, cont_name='collections', cont_id=result.get('_id'))
return results

def curators(self):
Expand Down Expand Up @@ -148,13 +159,23 @@ def get_sessions(self, cid):

if not self.is_true('archived'):
query['archived'] = {'$ne': True}
if not self.superuser_request:
query['permissions._id'] = self.uid

projection = self.container_handler_configurations['sessions']['list_projection']
if self.superuser_request:
permchecker = always_ok
elif self.public_request:
permchecker = containerauth.list_public_request
else:
permchecker = containerauth.list_permission_checker(self)

sessions = list(containerstorage.SessionStorage().get_all_el(query, None, projection))
projection = self.PHI_FIELDS.copy()
if self.is_true('phi'):
projection = None
phi = True
else:
phi = False

sessions = permchecker(containerstorage.SessionStorage().exec_op)('GET', query=query, public=self.public_request, projection=projection, phi=phi)
log.debug(sessions)
self._filter_all_permissions(sessions, self.uid)
if self.is_true('measurements'):
self._add_session_measurements(sessions)
Expand Down Expand Up @@ -183,11 +204,21 @@ def get_acquisitions(self, cid):

if not self.superuser_request:
query['permissions._id'] = self.uid
if self.superuser_request:
permchecker = always_ok
elif self.public_request:
permchecker = containerauth.list_public_request
else:
permchecker = containerauth.list_permission_checker(self)

projection = self.container_handler_configurations['acquisitions']['list_projection']

acquisitions = list(containerstorage.AcquisitionStorage().get_all_el(query, None, projection))
projection = self.PHI_FIELDS.copy()
if self.is_true('phi'):
projection = None
phi = True
else:
phi = False

acquisitions = permchecker(containerstorage.AcquisitionStorage().exec_op)('GET', query=query, public=self.public_request, projection=projection, phi=phi)
self._filter_all_permissions(acquisitions, self.uid)

for acquisition in acquisitions:
Expand Down
Loading