Skip to content
This repository has been archived by the owner on Mar 10, 2023. It is now read-only.

Commit

Permalink
Merge pull request #22 from ScilifelabDataCentre/develop
Browse files Browse the repository at this point in the history
Prepare for release of 0.3.0
  • Loading branch information
talavis authored Jun 13, 2020
2 parents a500f47 + d64bfa2 commit bdcb42f
Show file tree
Hide file tree
Showing 29 changed files with 1,041 additions and 523 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/dockerhub-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ jobs:
- name: Publish backend to Docker Hub
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: scilifelabdatacentre/covid-portal-backend
name: scilifelabdatacentre/data-tracker-backend
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
dockerfile: k8s/Dockerfile-backend
tags: "latest"
- name: Publish frontend to Docker Hub
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: scilifelabdatacentre/covid-portal-frontend
name: scilifelabdatacentre/data-tracker-frontend
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
dockerfile: k8s/Dockerfile-frontend
Expand Down
144 changes: 104 additions & 40 deletions backend/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ def get_random(amount: int = 1):
"""
results = list(flask.g.db['projects'].aggregate([{'$sample': {'size': amount}}]))

for result in results:
# only show owner if owner/admin
if not flask.g.current_user or\
(not user.has_permission('DATA_MANAGEMENT') and
flask.g.current_user['_id'] not in result['owners'] and
flask.g.current_user['email'] not in result['owners']):
logging.debug('Not allowed to access owners %s', flask.g.current_user)
del result['owners']

# return {_id, _title} for datasets
result['datasets'] = [flask.g.db.datasets.find_one({'_id': dataset},
{'title': 1})
for dataset in result['datasets']]
return utils.response_json({'projects': results})


Expand All @@ -49,12 +63,31 @@ def get_project(identifier):
"""
try:
uuid = utils.str_to_uuid(identifier)
result = flask.g.db['projects'].find_one({'_id': uuid})
except ValueError:
result = None
flask.abort(status=404)

result = flask.g.db['projects'].find_one({'_id': uuid})
if not result:
return flask.Response(status=404)

# only show owner if owner/admin
if not flask.g.current_user or\
(not user.has_permission('DATA_MANAGEMENT') and
flask.g.current_user['_id'] not in result['owners'] and
flask.g.current_user['email'] not in result['owners']):
logging.debug('Not allowed to access owners %s', flask.g.current_user)
del result['owners']
else:
for i, owner in enumerate(result['owners']):
if not utils.is_email(owner):
owner_info = flask.g.db['users'].find_one(owner)
result['owners'][i] = owner_info['email']

# return {_id, _title} for datasets
result['datasets'] = [flask.g.db.datasets.find_one({'_id': dataset},
{'title': 1})
for dataset in result['datasets']]

return utils.response_json({'project': result})


Expand All @@ -80,26 +113,30 @@ def add_project(): # pylint: disable=too-many-branches
if not validation[0]:
flask.abort(status=validation[1])

if 'owners' in indata and indata['owners']:
if not user.has_permission('DATA_MANAGEMENT'):
if len(indata['owners']) != 1:
flask.abort(status=400)
user_uuid = utils.str_to_uuid(indata['owners'][0])
if user_uuid != flask.g.current_user['_id']:
flask.abort(status=400)
else:
if 'title' not in indata:
flask.abort(status=400)

if not indata.get('owners'):
indata['owners'] = [flask.g.current_user['_id']]
else:
for i, owner in enumerate(indata['owners']):
if utils.is_email(owner):
owner_info = flask.g.db['users'].find_one({'email': owner})
if owner_info:
indata['owners'][i] = owner_info['_id']

if 'datasets' in indata:
if not user.has_permission('DATA_MANAGEMENT'):
for ds_uuid_str in indata['datasets']:
ds_uuid = utils.str_to_uuid(ds_uuid_str)
order_info = flask.g.db['orders'].find_one({'datasets': ds_uuid})
if not order_info:
flask.abort(status=400)
if order_info['creator'] != flask.g.current_user['_id'] and\
order_info['receiver'] != flask.g.current_user['_id']:
flask.abort(status=400)
for i, dataset_uuid_str in enumerate(indata['datasets']):
dataset_uuid = utils.str_to_uuid(dataset_uuid_str)
indata['datasets'][i] = dataset_uuid
# allow new ones only if owner or DATA_MANAGEMENT
order_info = flask.g.db['orders'].find_one({'datasets': dataset_uuid})
if not order_info:
flask.abort(status=400)
if not user.has_permission('DATA_MANAGEMENT') and\
order_info['creator'] != flask.g.current_user['_id'] and\
order_info['receiver'] != flask.g.current_user['_id']:
flask.abort(status=400)

project.update(indata)

Expand Down Expand Up @@ -150,16 +187,19 @@ def delete_project(identifier: str):
@user.login_required
def update_project(identifier): # pylint: disable=too-many-branches
"""
Add a project.
Update a project.
Args:
identifier (str): The project uuid.
Returns:
flask.Response: Json structure with the ``_id`` of the project.
flask.Response: Status code.
"""
try:
ds_uuid = utils.str_to_uuid(identifier)
project_uuid = utils.str_to_uuid(identifier)
except ValueError:
return flask.abort(status=404)
project = flask.g.db['projects'].find_one({'_id': ds_uuid})
project = flask.g.db['projects'].find_one({'_id': project_uuid})
if not project:
flask.abort(status=404)

Expand All @@ -170,32 +210,40 @@ def update_project(identifier): # pylint: disable=too-many-branches

# permission check
if not user.has_permission('DATA_MANAGEMENT'):
if flask.g.current_user['_id'] not in project['owners']:
if flask.g.current_user['_id'] not in project['owners'] and\
flask.g.current_user['email'] not in project['owners']:
logging.debug('Unauthorized update attempt (project %s, user %s)',
project_uuid,
flask.g.current_user['_id'])
flask.abort(status=403)

# indata validation
validation = utils.basic_check_indata(indata, project, prohibited=('_id'))
if not validation[0]:
flask.abort(status=validation[1])

if 'owners' in indata and indata['owners']:
if not user.has_permission('DATA_MANAGEMENT'):
if len(indata['owners']) != 1:
flask.abort(status=400)
user_uuid = utils.str_to_uuid(indata['owners'][0])
if user_uuid != flask.g.current_user['_id']:
flask.abort(status=400)
if indata.get('owners'):
for i, owner in enumerate(indata['owners']):
if utils.is_email(owner):
owner_info = flask.g.db['users'].find_one({'email': owner})
if owner_info:
indata['owners'][i] = owner_info['_id']

if 'datasets' in indata:
if not user.has_permission('DATA_MANAGEMENT'):
for ds_uuid_str in indata['datasets']:
ds_uuid = utils.str_to_uuid(ds_uuid_str)
order_info = flask.g.db['orders'].find_one({'datasets': ds_uuid})
if not order_info:
flask.abort(status=400)
if order_info['creator'] != flask.g.current_user['_id'] and\
order_info['receiver'] != flask.g.current_user['_id']:
flask.abort(status=400)
for i, dataset_uuid_str in enumerate(indata['datasets']):
dataset_uuid = utils.str_to_uuid(dataset_uuid_str)
indata['datasets'][i] = dataset_uuid
# do not reject existing datasets
if dataset_uuid in project['datasets']:
continue
# allow new ones only if owner or DATA_MANAGEMENT
order_info = flask.g.db['orders'].find_one({'datasets': dataset_uuid})
if not order_info:
flask.abort(status=400)
if not user.has_permission('DATA_MANAGEMENT') and\
order_info['creator'] != flask.g.current_user['_id'] and\
order_info['receiver'] != flask.g.current_user['_id']:
flask.abort(status=400)

project.update(indata)

Expand All @@ -206,3 +254,19 @@ def update_project(identifier): # pylint: disable=too-many-branches
utils.make_log('project', 'edit', 'Project updated', project)

return flask.Response(status=200)


@blueprint.route('/user/', methods=['GET'])
@user.login_required
def list_user_projects(): # pylint: disable=too-many-branches
"""
List project owned by the user.
Returns:
flask.Response: JSON structure.
"""
results = list(flask.g.db['projects']
.find({'$or': [{'owners': flask.g.current_user['_id']},
{'owners': flask.g.current_user['email']}]}))
logging.debug(results)
return utils.response_json({'projects': results})
80 changes: 64 additions & 16 deletions backend/tests/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,54 @@ def test_get_project(use_db):
"""
db = use_db
session = requests.Session()
for _ in range(10):
for _ in range(3):
project = list(db['projects'].aggregate([{'$sample': {'size': 1}}]))[0]
print(project)
owner_emails = [db['users'].find_one({'$or': [{'_id': identifier},
{'email': identifier}]})['email']
for identifier in project['owners']]
project['_id'] = str(project['_id'])
proj_owner = db['users'].find_one({'_id': project['owners'][0]})
if not proj_owner:
proj_owner = db['users'].find_one({'email': project['owners'][0]})
if not proj_owner:
print('Unknown user for owner')
assert False
project['owners'] = [str(entry) for entry in project['owners']]

project['datasets'] = [str(entry) for entry in project['datasets']]
response = make_request(session, f'/api/project/{project["_id"]}')
assert response.code == 200
for field in project:
assert project[field] == response.data['project'][field]
if field == 'datasets':
for i, ds_uuid in enumerate(project[field]):
assert ds_uuid == response.data['project'][field][i]['_id']
elif field != 'owners':
assert project[field] == response.data['project'][field]
else:
if field in response.data['project']:
assert response.data['project'][field] == owner_emails
as_user(session, proj_owner['auth_id'])
response = make_request(session, f'/api/project/{project["_id"]}')
assert response.code == 200
for field in project:
if field == 'datasets':
for i, ds_uuid in enumerate(project[field]):
assert ds_uuid == response.data['project'][field][i]['_id']
elif field == 'owners':
assert response.data['project'][field] == owner_emails
else:
assert project[field] == response.data['project'][field]
as_user(session, USERS['root'])
response = make_request(session, f'/api/project/{project["_id"]}')
assert response.code == 200
for field in project:
if field == 'datasets':
for i, ds_uuid in enumerate(project[field]):
assert ds_uuid == response.data['project'][field][i]['_id']
elif field == 'owners':
assert response.data['project'][field] == owner_emails
else:
assert project[field] == response.data['project'][field]


def test_get_project_bad():
Expand Down Expand Up @@ -109,16 +147,13 @@ def test_add_project_permissions(use_db):
data=indata,
ret_json=True)
for response in responses:
if response.role in ('base', 'data', 'root'):
assert response.code == 200
assert '_id' in response.data
assert len(response.data['_id']) == 36
elif response.role == 'no-login':
if response.role == 'no-login':
assert response.code == 401
assert not response.data
else:
assert response.code == 400
assert not response.data
assert response.code == 200
assert '_id' in response.data
assert len(response.data['_id']) == 36

dataset_info = next(db['datasets'].aggregate([{'$sample': {'size': 1}}]))
order_info = db['orders'].find_one({'datasets': dataset_info['_id']})
Expand Down Expand Up @@ -175,8 +210,7 @@ def test_add_project(use_db):
'contact': '[email protected]',
'dmp': 'https://dmp_url_test',
'owners': [str(user_info['_id'])],
'publications': [{'title': 'A test publication title',
'doi': 'doi://a_test_doi_value'}],
'publications': ['A test publication title, doi://a_test_doi_value'],
'title': 'Test title',
'datasets': [str(dataset_info['_id'])]}
indata.update(TEST_LABEL)
Expand Down Expand Up @@ -252,6 +286,22 @@ def test_add_project_bad():
assert not response.data


indata = {}
indata.update(TEST_LABEL)

responses = make_request_all_roles(f'/api/project/',
method='POST',
data=indata,
ret_json=True)
for response in responses:
if response.role == 'no-login':
assert response.code == 401
assert not response.data
else:
assert response.code == 400
assert not response.data


indata = {'bad_tag': 'content',
'title': 'title'}

Expand Down Expand Up @@ -361,8 +411,7 @@ def test_update_project(use_db):
'contact': '[email protected]',
'dmp': 'https://dmp_updated_url_test',
'owners': [str(project_info['owners'][0])],
'publications': [{'title': 'Updated publication title',
'doi': 'doi://updated_doi_value'}],
'publications': ['Updated publication title, doi://updated_doi_value'],
'title': 'Test title updated',
'datasets': [str(uuids[1])]}
indata.update(TEST_LABEL)
Expand Down Expand Up @@ -397,8 +446,7 @@ def test_update_project(use_db):
'contact': '[email protected]',
'dmp': 'https://dmp_updated_url_test2',
'owners': [str(user_info['_id'])],
'publications': [{'title': 'Updated publication title2',
'doi': 'doi://updated_doi_value'}],
'publications': ['Updated publication title2, doi://updated_doi_value'],
'title': 'Test title updated',
'datasets': [str(uuids[1]), str(uuids[1])]}
indata.update(TEST_LABEL)
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_list_info():
ret_json=True)
for response in responses:
assert response.code == 200
assert len(response.data['user']) == 4
assert len(response.data['user']) == 5
if response.role != 'no-login':
assert response.data['user']['name'] == f'{response.role.capitalize()} Test'

Expand Down
3 changes: 2 additions & 1 deletion backend/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def get_current_user_info():
"""
data = flask.g.current_user
outstructure = {'affiliation': '',
'auth_id': '',
'email': '',
'name': '',
'permissions': ''}
Expand Down Expand Up @@ -288,7 +289,7 @@ def update_current_user_info():
result = flask.g.db['users'].update_one({'_id': user_data['_id']},
{'$set': user_data})
if not result.acknowledged:
logging.error('User update failed: %s', indata)
logging.error('User self-update failed: %s', indata)
flask.Response(status=500)
else:
utils.make_log('user', 'edit', 'User self-updated', user_data)
Expand Down
2 changes: 2 additions & 0 deletions backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ def is_email(indata: str):
Returns:
bool: Is the indata an email address or not.
"""
if not isinstance(indata, str):
return False
return bool(REGEX['email'].search(indata))


Expand Down
Loading

0 comments on commit bdcb42f

Please sign in to comment.