api: allow mixing anonymous restriction and basic authentication (#53883)

This commit is contained in:
Frédéric Péters 2021-05-10 13:47:51 +02:00
parent 3a641631b1
commit e8a234da11
5 changed files with 64 additions and 34 deletions

View File

@ -58,6 +58,8 @@ coucou = 1234
'''
)
pub.user_class.wipe()
return pub
@ -67,7 +69,6 @@ def teardown_module(module):
@pytest.fixture
def local_user():
get_publisher().user_class.wipe()
user = get_publisher().user_class()
user.name = 'Jean Darmette'
user.email = 'jean.darmette@triffouilis.fr'
@ -78,7 +79,6 @@ def local_user():
@pytest.fixture
def admin_user():
get_publisher().user_class.wipe()
user = get_publisher().user_class()
user.name = 'John Doe Admin'
user.email = 'john.doe@example.com'
@ -788,7 +788,8 @@ def test_api_anonymized_formdata(pub, local_user, admin_user):
assert 'name' in resp.json['evolution'][1]['who']
def test_api_access_restrict_to_anonymised_data(pub, local_user):
@pytest.mark.parametrize('http_basic_auth', [False, True])
def test_api_access_restrict_to_anonymised_data(pub, local_user, http_basic_auth):
pub.role_class.wipe()
role = pub.role_class(name='test')
role.store()
@ -823,36 +824,56 @@ def test_api_access_restrict_to_anonymised_data(pub, local_user):
access.access_key = '12345'
access.store()
resp = get_app(pub).get(
sign_uri(
'/api/forms/test/list?full=on',
user=local_user,
orig=access.access_identifier,
key=access.access_key,
)
)
app = get_app(pub)
if http_basic_auth:
# there's not "defaults to admin" permissions in case of basic authentication.
access.roles = [role]
access.store()
def get_url(url, **kwargs):
app.set_authorization(('Basic', ('test', '12345')))
return app.get(url, **kwargs)
else:
def get_url(url, **kwargs):
return app.get(
sign_uri(url, user=local_user, orig=access.access_identifier, key=access.access_key), **kwargs
)
resp = get_url('/api/forms/test/list?full=on')
assert len(resp.json) == 10
assert resp.json[0]['fields']['foobar'] == 'FOO BAR1'
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
assert resp.json[0].get('user')
# get a single formdata
resp = get_url('/api/forms/test/%s/' % formdata.id)
assert 'user' in resp.json
# restrict API access to anonymised data
access.restrict_to_anonymised_data = True
access.store()
resp = get_app(pub).get(
sign_uri(
'/api/forms/test/list?full=on',
user=local_user,
orig=access.access_identifier,
key=access.access_key,
)
)
resp = get_url('/api/forms/test/list?full=on')
assert len(resp.json) == 10
assert 'foobar' not in resp.json[0]['fields']
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
assert not resp.json[0].get('user')
# get a single formdata
resp = get_url('/api/forms/test/%s/' % formdata.id)
assert 'user' not in resp.json
if http_basic_auth:
# for basic HTTP authentication, check there's no access if roles are not given.
access.roles = []
access.store()
get_url('/api/forms/test/list?full=on', status=403)
get_url('/api/forms/test/%s/' % formdata.id, status=403)
def test_api_geojson_formdata(pub, local_user):
pub.role_class.wipe()

View File

@ -189,17 +189,19 @@ class ApiFormPageMixin:
raise TraversalError()
def check_access(self, api_name=None):
if get_request().has_anonymised_data_api_restriction():
if not is_url_signed() or (get_request().user and get_request().user.is_admin):
raise AccessForbiddenError('user not authenticated')
else:
if get_request().user and get_request().user.is_admin:
return # grant access to admins, to ease debug
api_user = get_user_from_api_query_string(api_name=api_name)
if not api_user:
raise AccessForbiddenError('user not authenticated')
if not self.formdef.is_of_concern_for_user(api_user):
raise AccessForbiddenError('unsufficient roles')
if get_request().user and get_request().user.is_admin:
return # grant access to admins, to ease debug
if get_request().has_anonymised_data_api_restriction() and is_url_signed():
# when requesting anonymous data, a signature is enough
return
api_user = get_user_from_api_query_string(api_name=api_name)
if not api_user:
raise AccessForbiddenError('user not authenticated')
if not self.formdef.is_of_concern_for_user(api_user):
raise AccessForbiddenError('unsufficient roles')
def _q_lookup(self, component):
if component == 'ics':

View File

@ -89,6 +89,9 @@ class ApiAccess(XmlStorableObject):
is_api_user = True
anonymous = False
def __init__(self, api_access):
self.api_access = api_access
def can_go_in_admin(self):
return False
@ -98,7 +101,7 @@ class ApiAccess(XmlStorableObject):
def get_roles(self):
return self.roles
user = RestrictedApiUser()
user = RestrictedApiUser(self)
user.roles = [x.id for x in self.get_roles()]
return user

View File

@ -158,13 +158,12 @@ class FormStatusPage(Directory, FormTemplateMixin):
session = get_session()
mine = False
if api_call:
if get_request().has_anonymised_data_api_restriction():
user = get_user_from_api_query_string() or get_request().user
if get_request().has_anonymised_data_api_restriction() and (not user or not user.is_api_user):
if is_url_signed() or (get_request().user and get_request().user.is_admin):
return None
else:
raise errors.AccessUnauthorizedError()
else:
user = get_user_from_api_query_string() or get_request().user
else:
user = get_request().user
if user and not user.anonymous:

View File

@ -223,11 +223,16 @@ class HTTPRequest(quixote.http_request.HTTPRequest):
if 'anonymise' in self.form:
return True
orig = self.form.get('orig')
if orig:
api_access = ApiAccess.get_by_identifier(orig)
if api_access:
return api_access.restrict_to_anonymised_data
if self.user and self.user.is_api_user:
return self.user.api_access.restrict_to_anonymised_data
return False
@property