Projet

Général

Profil

0001-api-accept-HTTP-Basic-authentication-scheme-for-API-.patch

Frédéric Péters, 05 mai 2021 10:32

Télécharger (7,44 ko)

Voir les différences:

Subject: [PATCH] api: accept HTTP Basic authentication scheme for API accesses
 (#20624)

 tests/api/test_carddef.py          | 45 ++++++++++++++++++++++++++
 tests/api/test_workflow.py         | 52 ++++++++++++++++++++++++++++++
 tests/backoffice_pages/test_all.py | 14 ++++++++
 wcs/api_access.py                  | 10 ++++++
 wcs/api_utils.py                   |  3 ++
 wcs/qommon/http_request.py         |  9 +++++-
 6 files changed, 132 insertions(+), 1 deletion(-)
tests/api/test_carddef.py
262 262
    assert resp.json['err_desc'] == 'unsufficient roles'
263 263

  
264 264

  
265
def test_cards_http_auth_access(pub, local_user):
266
    pub.role_class.wipe()
267
    role = pub.role_class(name='test')
268
    role.store()
269

  
270
    CardDef.wipe()
271
    carddef = CardDef()
272
    carddef.name = 'test'
273
    carddef.fields = [fields.StringField(id='0', label='foobar', varname='foo')]
274
    carddef.workflow_roles = {'_viewer': role.id}
275
    carddef.store()
276

  
277
    carddef.data_class().wipe()
278
    formdata = carddef.data_class()()
279
    formdata.data = {'0': 'blah'}
280
    formdata.just_created()
281
    formdata.store()
282

  
283
    access = ApiAccess()
284
    access.name = 'test'
285
    access.access_identifier = 'test'
286
    access.access_key = '12345'
287
    access.store()
288

  
289
    app = get_app(pub)
290
    app.set_authorization(('Basic', ('test', '12345')))
291

  
292
    # no role restrictions, no admin
293
    resp = app.get('/api/cards/test/list', status=403)
294

  
295
    # restricted to the correct role, get it
296
    access.roles = [role]
297
    access.store()
298
    resp = app.get('/api/cards/test/list')
299
    assert len(resp.json['data']) == 1
300

  
301
    # restricted to another role, do not get it
302
    role2 = pub.role_class(name='second')
303
    role2.store()
304
    access.roles = [role2]
305
    access.store()
306
    resp = app.get('/api/cards/test/list', status=403)
307
    assert resp.json['err_desc'] == 'unsufficient roles'
308

  
309

  
265 310
def test_post_invalid_json(pub, local_user):
266 311
    resp = get_app(pub).post(
267 312
        '/api/cards/test/submit', params='not a json payload', content_type='application/json', status=400
tests/api/test_workflow.py
319 319
    assert formdef.data_class().get(formdata.id).evolution[-1].who is None
320 320

  
321 321

  
322
def test_workflow_trigger_http_auth_access(pub, local_user):
323
    pub.role_class.wipe()
324
    role = pub.role_class(name='xxx')
325
    role.store()
326
    role2 = pub.role_class(name='xxx2')
327
    role2.store()
328

  
329
    workflow = Workflow(name='test')
330
    st1 = workflow.add_status('Status1', 'st1')
331
    jump = JumpWorkflowStatusItem()
332
    jump.trigger = 'XXX'
333
    jump.status = 'st2'
334
    st1.items.append(jump)
335
    jump.parent = st1
336
    workflow.add_status('Status2', 'st2')
337
    workflow.store()
338

  
339
    FormDef.wipe()
340
    formdef = FormDef()
341
    formdef.name = 'test'
342
    formdef.fields = []
343
    formdef.workflow_id = workflow.id
344
    formdef.store()
345

  
346
    formdef.data_class().wipe()
347
    formdata = formdef.data_class()()
348
    formdata.just_created()
349
    formdata.store()
350

  
351
    jump.by = [role.id]
352
    workflow.store()
353

  
354
    access = ApiAccess()
355
    access.name = 'test'
356
    access.access_identifier = 'test'
357
    access.access_key = '12345'
358
    access.roles = [role2]
359
    access.store()
360

  
361
    app = get_app(pub)
362
    app.set_authorization(('Basic', ('test', '12345')))
363
    app.post(formdata.get_url() + 'jump/trigger/XXX/', status=403)
364
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'  # no change
365

  
366
    access.roles = [role]
367
    access.store()
368

  
369
    app.post(formdata.get_url() + 'jump/trigger/XXX/', headers={'accept': 'application/json'}, status=200)
370
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
371
    assert formdef.data_class().get(formdata.id).evolution[-1].who is None
372

  
373

  
322 374
def test_workflow_global_webservice_trigger(pub, local_user):
323 375
    workflow = Workflow(name='test')
324 376
    workflow.add_status('Status1', 'st1')
tests/backoffice_pages/test_all.py
16 16

  
17 17
import wcs.qommon.storage as st
18 18
from wcs import fields
19
from wcs.api_access import ApiAccess
19 20
from wcs.blocks import BlockDef
20 21
from wcs.carddef import CardDef
21 22
from wcs.categories import Category
......
6227 6228

  
6228 6229
    resp = resp.forms['listing-settings'].submit()
6229 6230
    assert resp.text.count('<tr') == 6
6231

  
6232

  
6233
def test_backoffice_http_basic_auth(pub):
6234
    access = ApiAccess()
6235
    access.name = 'test'
6236
    access.access_identifier = 'test'
6237
    access.access_key = '12345'
6238
    access.store()
6239

  
6240
    create_superuser(pub)
6241
    app = get_app(pub)
6242
    app.set_authorization(('Basic', ('test', '12345')))
6243
    app.get('/backoffice/', status=403)
wcs/api_access.py
88 88
            is_admin = False
89 89
            is_api_user = True
90 90

  
91
            def can_go_in_backoffice(self):
92
                return False
93

  
91 94
            def get_roles(self):
92 95
                return self.roles
93 96

  
94 97
        user = RestrictedApiUser()
95 98
        user.roles = [x.id for x in self.get_roles()]
96 99
        return user
100

  
101
    @classmethod
102
    def get_with_credentials(cls, username, password):
103
        api_access = cls.get_by_identifier(username)
104
        if not api_access or api_access.access_key != password:
105
            raise KeyError
106
        return api_access.get_as_api_user()
wcs/api_utils.py
123 123
def get_user_from_api_query_string(api_name=None):
124 124
    # check signature or auth header
125 125
    if not is_url_signed():
126
        user = getattr(get_request(), 'user', None)
127
        if user and user.is_api_user:
128
            return user
126 129
        if api_name:
127 130
            check_http_basic_auth(api_name)
128 131
        else:
wcs/qommon/http_request.py
61 61
                # padding or invalid base64-encoded string).
62 62
                self._user = None
63 63
                return
64

  
65
            from wcs.api_access import ApiAccess
66

  
64 67
            from .ident.password_accounts import PasswordAccount
65 68

  
66 69
            try:
67 70
                self._user = PasswordAccount.get_with_credentials(username, password)
68 71
            except KeyError:
69
                self._user = None
72
                try:
73
                    self._user = ApiAccess.get_with_credentials(username, password)
74
                except KeyError:
75
                    self._user = None
76

  
70 77
            return
71 78

  
72 79
        try:
73
-