Projet

Général

Profil

0002-add-new-parameter-anonymise-to-API-api-forms-slug-en.patch

Benjamin Dauvergne, 26 février 2016 11:06

Télécharger (8,44 ko)

Voir les différences:

Subject: [PATCH 2/2] add new parameter anonymise to API /api/forms/<slug>/
 endpoints 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.
 help/fr/api-get.page | 19 +++++++++++++
 tests/test_api.py    | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 wcs/api.py           | 24 +++++++++-------
 3 files changed, 109 insertions(+), 11 deletions(-)
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&amp;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
620 620

  
621 621
    assert [x.get('id') for x in resp.json['roles']['_receiver']] == [str(role.id)]
622 622
    assert [x.get('id') for x in resp.json['roles']['_foobar']] == [str(another_role.id)]
623
    assert [x.get('id') for x in resp.json['roles']['concerned']] == [str(role.id), str(another_role.id)]
623
    assert (set([x.get('id') for x in resp.json['roles']['concerned']])
624
            == set([str(role.id), str(another_role.id)]))
624 625
    assert [x.get('id') for x in resp.json['roles']['actions']] == [str(role.id)]
625 626

  
626 627
    # check the ?format=json endpoint returns 403
......
732 733
        upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
733 734
        upload.receive(['base64me'])
734 735
        formdata.data = {'0': 'FOO BAR %d' % i, '2': upload}
736
        formdata.user_id = local_user.id
735 737
        if i%4 == 0:
736 738
            formdata.data['1'] = 'foo'
737 739
            formdata.data['1_display'] = 'foo'
......
772 774
    assert 'receipt_time' in resp.json[0]
773 775
    assert 'fields' in resp.json[0]
774 776
    assert 'file' not in resp.json[0]['fields'] # no file export in full lists
777
    assert 'user' in resp.json[0]
775 778

  
776 779
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission']['backoffice'] is True
777 780
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission']['channel'] == 'Mail'
......
794 797
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=all', user=local_user))
795 798
    assert len(resp.json) == 30
796 799

  
800
def test_api_anonymized_formdata(pub, local_user):
801
    Role.wipe()
802
    role = Role(name='test')
803
    role.store()
804

  
805
    FormDef.wipe()
806
    formdef = FormDef()
807
    formdef.name = 'test'
808
    formdef.workflow_roles = {'_receiver': role.id}
809
    formdef.fields = [
810
        fields.StringField(id='0', label='foobar', varname='foobar'),
811
        fields.ItemField(id='1', label='foobar3', varname='foobar3', type='item',
812
            items=['foo', 'bar', 'baz']),
813
        fields.FileField(id='2', label='foobar4', varname='file'),
814
        ]
815
    formdef.store()
816

  
817
    data_class = formdef.data_class()
818
    data_class.wipe()
819

  
820
    for i in range(30):
821
        formdata = data_class()
822
        date = time.strptime('2014-01-20', '%Y-%m-%d')
823
        upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
824
        upload.receive(['base64me'])
825
        formdata.data = {'0': 'FOO BAR %d' % i, '2': upload}
826
        formdata.user_id = local_user.id
827
        if i%4 == 0:
828
            formdata.data['1'] = 'foo'
829
            formdata.data['1_display'] = 'foo'
830
        elif i%4 == 1:
831
            formdata.data['1'] = 'bar'
832
            formdata.data['1_display'] = 'bar'
833
        else:
834
            formdata.data['1'] = 'baz'
835
            formdata.data['1_display'] = 'baz'
836

  
837
        formdata.just_created()
838
        if i%3 == 0:
839
            formdata.jump_status('new')
840
        else:
841
            formdata.jump_status('finished')
842
        formdata.store()
843

  
844
    # check access is granted even if the user has not the appropriate role
845
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on', user=local_user))
846
    assert len(resp.json) == 30
847
    assert 'receipt_time' in resp.json[0]
848
    assert 'fields' in resp.json[0]
849
    assert 'user' not in resp.json[0]
850
    assert 'file' not in resp.json[0]['fields'] # no file export in full lists
851
    assert 'foobar3' in resp.json[0]['fields']
852
    assert 'foobar' not in resp.json[0]['fields']
853

  
854
    # check access is granted event if there is no user
855
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on'))
856
    assert len(resp.json) == 30
857
    assert 'receipt_time' in resp.json[0]
858
    assert 'fields' in resp.json[0]
859
    assert 'user' not in resp.json[0]
860
    assert 'file' not in resp.json[0]['fields'] # no file export in full lists
861
    assert 'foobar3' in resp.json[0]['fields']
862
    assert 'foobar' not in resp.json[0]['fields']
863
    # check anonymise is enforced on detail view
864
    resp = get_app(pub).get(sign_uri('/api/forms/%s/?anonymise&full=on' % resp.json[0]['id']))
865
    assert 'receipt_time' in resp.json
866
    assert 'fields' in resp.json
867
    assert 'user' not in resp.json
868
    assert 'file' not in resp.json['fields'] # no file export in detail
869
    assert 'foobar3' in resp.json['fields']
870
    assert 'foobar' not in resp.json['fields']
871

  
797 872
def test_roles(pub, local_user):
798 873
    Role.wipe()
799 874
    role = Role(name='Hello World')
wcs/api.py
159 159

  
160 160

  
161 161
class ApiFormPage(BackofficeFormPage):
162
    _q_exports = [('list', 'json')] # same as backoffice but restricted to json export
162
    _q_exports = [('list', 'json')] # restrict to API endpoints
163 163

  
164 164
    def __init__(self, component):
165 165
        try:
......
170 170
        # otherwise be accessible if the user is the submitter.
171 171
        self.check_access()
172 172

  
173

  
173 174
    def check_access(self):
174
        api_user = get_user_from_api_query_string()
175
        if not api_user:
176
            if get_request().user and get_request().user.is_admin:
177
                return # grant access to admins, to ease debug
178
            raise AccessForbiddenError('user not authenticated')
179
        if not self.formdef.is_of_concern_for_user(api_user):
180
            raise AccessForbiddenError('unsufficient roles')
175
        if 'anonymise' in get_request().form:
176
            if not is_url_signed() or (get_request().user and get_request().user.is_admin):
177
                raise AccessForbiddenError('user not authenticated')
178
        else:
179
            api_user = get_user_from_api_query_string()
180
            if not api_user:
181
                if get_request().user and get_request().user.is_admin:
182
                    return # grant access to admins, to ease debug
183
                raise AccessForbiddenError('user not authenticated')
184
            if not self.formdef.is_of_concern_for_user(api_user):
185
                raise AccessForbiddenError('unsufficient roles')
181 186

  
182 187
    def _q_lookup(self, component):
183 188
        try:
......
189 194

  
190 195
class ApiFormsDirectory(Directory):
191 196
    def _q_lookup(self, component):
192
        api_user = get_user_from_api_query_string()
193
        if not api_user:
197
        if not is_url_signed():
194 198
            # grant access to admins, to ease debug
195 199
            if not (get_request().user and get_request().user.is_admin):
196 200
                raise AccessForbiddenError('user not authenticated')
197
-