From e8c6bb1514ee9ab7449fea4114ce25d5b842d323 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Sun, 6 Dec 2015 15:21:19 +0100 Subject: [PATCH 4/4] add new API /api/forms//anonymized returning anonymized formdata (#9146) Service is open to any request which bear a valid signature, no need to authenticate as a known user. It's also open to authenticated admin users for debugging. --- tests/test_api.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ wcs/api.py | 18 ++++++++++++--- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 11681e3..edcce73 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -663,6 +663,7 @@ def test_api_list_formdata(pub, local_user): upload = PicklableUpload('test.txt', 'text/plain', 'ascii') upload.receive(['base64me']) formdata.data = {'0': 'FOO BAR %d' % i, '2': upload} + formdata.user_id = local_user.id if i%4 == 0: formdata.data['1'] = 'foo' formdata.data['1_display'] = 'foo' @@ -699,6 +700,7 @@ def test_api_list_formdata(pub, local_user): assert 'receipt_time' in resp.json[0] assert 'fields' in resp.json[0] assert 'file' not in resp.json[0]['fields'] # no file export in full lists + assert 'user' in resp.json[0] # check filtered results resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=foo', user=local_user)) @@ -716,6 +718,70 @@ def test_api_list_formdata(pub, local_user): resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=all', user=local_user)) assert len(resp.json) == 30 +def test_api_anonymized_formdata(pub, local_user): + Role.wipe() + role = Role(name='test') + role.store() + + FormDef.wipe() + formdef = FormDef() + formdef.name = 'test' + formdef.workflow_roles = {'_receiver': role.id} + formdef.fields = [ + fields.StringField(id='0', label='foobar', varname='foobar'), + fields.ItemField(id='1', label='foobar3', varname='foobar3', type='item', + items=['foo', 'bar', 'baz']), + fields.FileField(id='2', label='foobar4', varname='file'), + ] + formdef.store() + + data_class = formdef.data_class() + data_class.wipe() + + for i in range(30): + formdata = data_class() + date = time.strptime('2014-01-20', '%Y-%m-%d') + upload = PicklableUpload('test.txt', 'text/plain', 'ascii') + upload.receive(['base64me']) + formdata.data = {'0': 'FOO BAR %d' % i, '2': upload} + formdata.user_id = local_user.id + if i%4 == 0: + formdata.data['1'] = 'foo' + formdata.data['1_display'] = 'foo' + elif i%4 == 1: + formdata.data['1'] = 'bar' + formdata.data['1_display'] = 'bar' + else: + formdata.data['1'] = 'baz' + formdata.data['1_display'] = 'baz' + + formdata.just_created() + if i%3 == 0: + formdata.jump_status('new') + else: + formdata.jump_status('finished') + formdata.store() + + # check access is granted even if the user has not the appropriate role + resp = get_app(pub).get(sign_uri('/api/forms/test/anonymized', user=local_user)) + assert len(resp.json) == 30 + assert 'receipt_time' in resp.json[0] + assert 'fields' in resp.json[0] + assert 'user' not in resp.json[0] + assert 'file' not in resp.json[0]['fields'] # no file export in full lists + assert 'foobar3' in resp.json[0]['fields'] + assert 'foobar' not in resp.json[0]['fields'] + + # check access is granted event if there is no user + resp = get_app(pub).get(sign_uri('/api/forms/test/anonymized')) + assert len(resp.json) == 30 + assert 'receipt_time' in resp.json[0] + assert 'fields' in resp.json[0] + assert 'user' not in resp.json[0] + assert 'file' not in resp.json[0]['fields'] # no file export in full lists + assert 'foobar3' in resp.json[0]['fields'] + assert 'foobar' not in resp.json[0]['fields'] + def test_roles(pub, local_user): Role.wipe() role = Role(name='Hello World') diff --git a/wcs/api.py b/wcs/api.py index 7412435..fa0bc8c 100644 --- a/wcs/api.py +++ b/wcs/api.py @@ -156,7 +156,9 @@ class ApiFormdataPage(FormStatusPage): class ApiFormPage(BackofficeFormPage): - _q_exports = [('list', 'json')] # same as backoffice but restricted to json export + _q_exports = [('list', 'json'), # same as backoffice but restricted to json export + ('anonymized', 'anonymised'), + ] def __init__(self, component): try: @@ -173,6 +175,17 @@ class ApiFormPage(BackofficeFormPage): if not self.formdef.is_of_concern_for_user(api_user): raise AccessForbiddenError('unsufficient roles') + def anonymised(self): + get_response().set_content_type('application/json') + if (not (get_request().user and get_request().user.is_admin) and + not is_url_signed()): + raise AccessForbiddenError('user not authenticated') + output = [filled.get_json_export_dict(include_files=False, anonymise=True) for filled in + self.formdef.data_class().select()] + return json.dumps(output, + cls=misc.JSONEncoder, + encoding=get_publisher().site_charset) + def _q_lookup(self, component): # check access for all paths, to block access to formdata that would # otherwise be accessible if the user is the submitter. @@ -186,8 +199,7 @@ class ApiFormPage(BackofficeFormPage): class ApiFormsDirectory(Directory): def _q_lookup(self, component): - api_user = get_user_from_api_query_string() - if not api_user: + if not is_url_signed(): # grant access to admins, to ease debug if not (get_request().user and get_request().user.is_admin): raise AccessForbiddenError('user not authenticated') -- 2.1.4