0002-add-new-parameter-anonymise-to-API-api-forms-slug-en.patch
help/fr/api-get.page | ||
---|---|---|
294 | 294 | |
295 | 295 |
</section> |
296 | 296 | |
297 |
<section> |
|
298 |
<title>Données anonymisées</title> |
|
299 | ||
300 |
<p> |
|
301 |
Les APIs « List de formulaires » et le mode Pull de récupération d'un formulaire accepte un |
|
302 |
paramètre supplémentaire <code>anonymise</code>. Quand celui-ci est présent des données anonymisées |
|
303 |
des formulaires sont renvoyées et les contrôles d'accès sont simplifiés à une signature simple, il |
|
304 |
n'est pas nécessaire de préciser l'identifiant d'un utilisateur. |
|
305 |
<p> |
|
306 | ||
307 |
<screen> |
|
308 |
<output style="prompt">$ </output><input>curl -H "Accept: application/json" \ |
|
309 |
https://www.example.net/api/forms/inscriptions/list?full=on&anonymise</input> |
|
310 |
<output style="prompt">$ </output><input>curl -H "Accept: application/json" \ |
|
311 |
https://www.example.net/api/forms/inscriptions/10/?anonymise</input> |
|
312 |
</screen> |
|
313 | ||
314 |
</section> |
|
315 | ||
297 | 316 | |
298 | 317 |
</page> |
tests/test_api.py | ||
---|---|---|
898 | 898 |
upload = PicklableUpload('test.txt', 'text/plain', 'ascii') |
899 | 899 |
upload.receive(['base64me']) |
900 | 900 |
formdata.data = {'0': 'FOO BAR %d' % i, '2': upload} |
901 |
formdata.user_id = local_user.id |
|
901 | 902 |
if i%4 == 0: |
902 | 903 |
formdata.data['1'] = 'foo' |
903 | 904 |
formdata.data['1_display'] = 'foo' |
... | ... | |
938 | 939 |
assert 'receipt_time' in resp.json[0] |
939 | 940 |
assert 'fields' in resp.json[0] |
940 | 941 |
assert 'file' not in resp.json[0]['fields'] # no file export in full lists |
942 |
assert 'user' in resp.json[0] |
|
941 | 943 | |
942 | 944 |
assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission']['backoffice'] is True |
943 | 945 |
assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission']['channel'] == 'Mail' |
... | ... | |
960 | 962 |
resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=all', user=local_user)) |
961 | 963 |
assert len(resp.json) == 30 |
962 | 964 | |
965 |
def test_api_anonymized_formdata(pub, local_user): |
|
966 |
Role.wipe() |
|
967 |
role = Role(name='test') |
|
968 |
role.store() |
|
969 | ||
970 |
FormDef.wipe() |
|
971 |
formdef = FormDef() |
|
972 |
formdef.name = 'test' |
|
973 |
formdef.workflow_roles = {'_receiver': role.id} |
|
974 |
formdef.fields = [ |
|
975 |
fields.StringField(id='0', label='foobar', varname='foobar'), |
|
976 |
fields.ItemField(id='1', label='foobar3', varname='foobar3', type='item', |
|
977 |
items=['foo', 'bar', 'baz']), |
|
978 |
fields.FileField(id='2', label='foobar4', varname='file'), |
|
979 |
] |
|
980 |
formdef.store() |
|
981 | ||
982 |
data_class = formdef.data_class() |
|
983 |
data_class.wipe() |
|
984 | ||
985 |
for i in range(30): |
|
986 |
formdata = data_class() |
|
987 |
date = time.strptime('2014-01-20', '%Y-%m-%d') |
|
988 |
upload = PicklableUpload('test.txt', 'text/plain', 'ascii') |
|
989 |
upload.receive(['base64me']) |
|
990 |
formdata.data = {'0': 'FOO BAR %d' % i, '2': upload} |
|
991 |
formdata.user_id = local_user.id |
|
992 |
if i%4 == 0: |
|
993 |
formdata.data['1'] = 'foo' |
|
994 |
formdata.data['1_display'] = 'foo' |
|
995 |
elif i%4 == 1: |
|
996 |
formdata.data['1'] = 'bar' |
|
997 |
formdata.data['1_display'] = 'bar' |
|
998 |
else: |
|
999 |
formdata.data['1'] = 'baz' |
|
1000 |
formdata.data['1_display'] = 'baz' |
|
1001 | ||
1002 |
formdata.just_created() |
|
1003 |
if i%3 == 0: |
|
1004 |
formdata.jump_status('new') |
|
1005 |
else: |
|
1006 |
formdata.jump_status('finished') |
|
1007 |
formdata.store() |
|
1008 | ||
1009 |
# check access is granted even if the user has not the appropriate role |
|
1010 |
resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on', user=local_user)) |
|
1011 |
assert len(resp.json) == 30 |
|
1012 |
assert 'receipt_time' in resp.json[0] |
|
1013 |
assert 'fields' in resp.json[0] |
|
1014 |
assert 'user' not in resp.json[0] |
|
1015 |
assert 'file' not in resp.json[0]['fields'] # no file export in full lists |
|
1016 |
assert 'foobar3' in resp.json[0]['fields'] |
|
1017 |
assert 'foobar' not in resp.json[0]['fields'] |
|
1018 | ||
1019 |
# check access is granted event if there is no user |
|
1020 |
resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on')) |
|
1021 |
assert len(resp.json) == 30 |
|
1022 |
assert 'receipt_time' in resp.json[0] |
|
1023 |
assert 'fields' in resp.json[0] |
|
1024 |
assert 'user' not in resp.json[0] |
|
1025 |
assert 'file' not in resp.json[0]['fields'] # no file export in full lists |
|
1026 |
assert 'foobar3' in resp.json[0]['fields'] |
|
1027 |
assert 'foobar' not in resp.json[0]['fields'] |
|
1028 |
# check anonymise is enforced on detail view |
|
1029 |
resp = get_app(pub).get(sign_uri('/api/forms/%s/?anonymise&full=on' % resp.json[0]['id'])) |
|
1030 |
assert 'receipt_time' in resp.json |
|
1031 |
assert 'fields' in resp.json |
|
1032 |
assert 'user' not in resp.json |
|
1033 |
assert 'file' not in resp.json['fields'] # no file export in detail |
|
1034 |
assert 'foobar3' in resp.json['fields'] |
|
1035 |
assert 'foobar' not in resp.json['fields'] |
|
1036 | ||
963 | 1037 |
def test_roles(pub, local_user): |
964 | 1038 |
Role.wipe() |
965 | 1039 |
role = Role(name='Hello World') |
wcs/api.py | ||
---|---|---|
52 | 52 | |
53 | 53 | |
54 | 54 |
class ApiFormPage(BackofficeFormPage): |
55 |
_q_exports = [('list', 'json')] # same as backoffice but restricted to json export
|
|
55 |
_q_exports = [('list', 'json')] # restrict to API endpoints
|
|
56 | 56 | |
57 | 57 |
def __init__(self, component): |
58 | 58 |
try: |
... | ... | |
63 | 63 |
# otherwise be accessible if the user is the submitter. |
64 | 64 |
self.check_access() |
65 | 65 | |
66 | ||
66 | 67 |
def check_access(self): |
67 |
api_user = get_user_from_api_query_string() |
|
68 |
if not api_user: |
|
69 |
if get_request().user and get_request().user.is_admin: |
|
70 |
return # grant access to admins, to ease debug |
|
71 |
raise AccessForbiddenError('user not authenticated') |
|
72 |
if not self.formdef.is_of_concern_for_user(api_user): |
|
73 |
raise AccessForbiddenError('unsufficient roles') |
|
68 |
if 'anonymise' in get_request().form: |
|
69 |
if not is_url_signed() or (get_request().user and get_request().user.is_admin): |
|
70 |
raise AccessForbiddenError('user not authenticated') |
|
71 |
else: |
|
72 |
api_user = get_user_from_api_query_string() |
|
73 |
if not api_user: |
|
74 |
if get_request().user and get_request().user.is_admin: |
|
75 |
return # grant access to admins, to ease debug |
|
76 |
raise AccessForbiddenError('user not authenticated') |
|
77 |
if not self.formdef.is_of_concern_for_user(api_user): |
|
78 |
raise AccessForbiddenError('unsufficient roles') |
|
74 | 79 | |
75 | 80 |
def _q_lookup(self, component): |
76 | 81 |
try: |
... | ... | |
82 | 87 | |
83 | 88 |
class ApiFormsDirectory(Directory): |
84 | 89 |
def _q_lookup(self, component): |
85 |
api_user = get_user_from_api_query_string() |
|
86 |
if not api_user: |
|
90 |
if not is_url_signed(): |
|
87 | 91 |
# grant access to admins, to ease debug |
88 | 92 |
if not (get_request().user and get_request().user.is_admin): |
89 | 93 |
raise AccessForbiddenError('user not authenticated') |
90 |
- |