Projet

Général

Profil

0001-api-add-possibility-of-http-basic-auth-access-to-the.patch

Frédéric Péters, 26 juillet 2017 15:07

Télécharger (7,77 ko)

Voir les différences:

Subject: [PATCH] api: add possibility of http basic auth access to the ics
 endpoint (#16792)

 tests/test_api.py            | 46 ++++++++++++++++++++++++++++++++++++++------
 wcs/api.py                   | 14 +++++---------
 wcs/api_utils.py             | 18 ++++++++++++++---
 wcs/backoffice/management.py |  5 ++---
 4 files changed, 62 insertions(+), 21 deletions(-)
tests/test_api.py
1445 1445
    formdef.store()
1446 1446
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user), status=404)
1447 1447

  
1448
def test_api_ics_formdata(pub, local_user):
1448
@pytest.fixture
1449
def ics_data(local_user):
1449 1450
    Role.wipe()
1450 1451
    role = Role(name='test')
1451 1452
    role.store()
......
1462 1463
    data_class = formdef.data_class()
1463 1464
    data_class.wipe()
1464 1465

  
1465
    # check access is denied if the user has not the appropriate role
1466
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar', user=local_user), status=403)
1467
    # even if there's an anonymse parameter
1468
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar?anonymise', user=local_user), status=403)
1469

  
1470 1466
    date = datetime.datetime(2014, 1, 20, 12, 00)
1471 1467
    for i in range(30):
1472 1468
        formdata = data_class()
......
1479 1475
            formdata.jump_status('finished')
1480 1476
        formdata.store()
1481 1477

  
1478
def test_api_ics_formdata(pub, local_user, ics_data):
1479
    role = Role.select()[0]
1480

  
1481
    # check access is denied if the user has not the appropriate role
1482
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar', user=local_user), status=403)
1483
    # even if there's an anonymse parameter
1484
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar?anonymise', user=local_user), status=403)
1485

  
1482 1486
    # add proper role to user
1483 1487
    local_user.roles = [role.id]
1484 1488
    local_user.store()
......
1495 1499
    # check 404 on erroneous field var
1496 1500
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/xxx', user=local_user), status=404)
1497 1501

  
1502
def test_api_ics_formdata_http_auth(pub, local_user, ics_data):
1503
    role = Role.select()[0]
1504

  
1505
    # no access
1506
    app = get_app(pub)
1507
    app.authorization = ('Basic', ('user', 'password'))
1508
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=403)
1509

  
1510
    # add authentication info
1511
    pub.load_site_options()
1512
    pub.site_options.add_section('api-http-auth-ics')
1513
    pub.site_options.set('api-http-auth-ics', 'user', 'password')
1514
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
1515

  
1516
    # check access is denied if the user has not the appropriate role
1517
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=403)
1518

  
1519
    # add proper role to user
1520
    local_user.roles = [role.id]
1521
    local_user.store()
1522

  
1523
    # check it gets the data
1524
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=200)
1525
    assert resp.headers['content-type'] == 'text/calendar; charset=utf-8'
1526
    assert resp.body.count('BEGIN:VEVENT') == 10
1527

  
1528
    # check it fails with a different password
1529
    app.authorization = ('Basic', ('user', 'password2'))
1530
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=403)
1531

  
1498 1532
def test_roles(pub, local_user):
1499 1533
    Role.wipe()
1500 1534
    role = Role(name='Hello World')
wcs/api.py
117 117
            self.formdef = FormDef.get_by_urlname(component)
118 118
        except KeyError:
119 119
            raise TraversalError()
120
        # check access for all paths, to block access to formdata that would
121
        # otherwise be accessible if the user is the submitter.
122
        self.check_access()
123 120

  
124
    def check_access(self):
121
    def check_access(self, api_name=None):
125 122
        if 'anonymise' in get_request().form:
126 123
            if not is_url_signed() or (get_request().user and get_request().user.is_admin):
127 124
                raise AccessForbiddenError('user not authenticated')
128 125
        else:
129
            api_user = get_user_from_api_query_string()
126
            api_user = get_user_from_api_query_string(api_name=api_name)
130 127
            if not api_user:
131 128
                if get_request().user and get_request().user.is_admin:
132 129
                    return # grant access to admins, to ease debug
......
138 135
        if component == 'ics':
139 136
            return self.ics()
140 137

  
138
        # check access for all paths, to block access to formdata that would
139
        # otherwise be accessible if the user is the submitter.
140
        self.check_access()
141 141
        try:
142 142
            formdata = self.formdef.data_class().get(component)
143 143
        except KeyError:
......
147 147

  
148 148
class ApiFormsDirectory(Directory):
149 149
    def _q_lookup(self, component):
150
        if not is_url_signed():
151
            # grant access to admins, to ease debug
152
            if not (get_request().user and get_request().user.is_admin):
153
                raise AccessForbiddenError('user not authenticated')
154 150
        return ApiFormPage(component)
155 151

  
156 152

  
wcs/api_utils.py
98 98
    return True
99 99

  
100 100

  
101
def get_user_from_api_query_string():
102
    if not is_url_signed():
101
def get_user_from_api_query_string(api_name=None):
102
    auth_header = get_request().get_header('Authorization', '')
103
    if auth_header:
104
        if not auth_header.startswith('Basic '):
105
            # we do not handle other authentication schemes
106
            raise AccessForbiddenError('unhandled authorization header')
107
        auth_header = auth_header.split(' ', 1)[1]
108
        username, password = base64.decodestring(auth_header).split(':', 1)
109
        configured_password = get_publisher().get_site_option(
110
                username, section='api-http-auth-%s' % api_name)
111
        if configured_password != password:
112
            raise AccessForbiddenError('invalid authorization')
113
    elif not is_url_signed():
103 114
        return None
104
    # Signature is good. Now looking for the user, by email/NameID.
115
    # Signature or auth header are ok.
116
    # Look for the user, by email/NameID.
105 117
    user = None
106 118
    if get_request().form.get('email'):
107 119
        email = get_request().form.get('email')
wcs/backoffice/management.py
1604 1604
        if 'anonymise' in get_request().form:
1605 1605
            # api/ will let this pass but we don't want that.
1606 1606
            raise errors.AccessForbiddenError()
1607
        self.check_access()
1607
        self.check_access('ics')
1608
        user = get_user_from_api_query_string('ics') or get_request().user
1608 1609

  
1609 1610
        formdef = self.formdef
1610 1611
        selected_filter = self.get_filter_from_query()
......
1625 1626
                else:
1626 1627
                    raise errors.TraversalError()
1627 1628

  
1628
                user = get_user_from_api_query_string() or get_request().user
1629

  
1630 1629
                formdatas, total_count = FormDefUI(formdef).get_listing_items(
1631 1630
                        selected_filter, user=user, query=query, criterias=criterias)
1632 1631

  
1633
-