api: allow mixing anonymous restriction and basic authentication (#53883)
This commit is contained in:
parent
3a641631b1
commit
e8a234da11
|
@ -58,6 +58,8 @@ coucou = 1234
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pub.user_class.wipe()
|
||||||
|
|
||||||
return pub
|
return pub
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,7 +69,6 @@ def teardown_module(module):
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def local_user():
|
def local_user():
|
||||||
get_publisher().user_class.wipe()
|
|
||||||
user = get_publisher().user_class()
|
user = get_publisher().user_class()
|
||||||
user.name = 'Jean Darmette'
|
user.name = 'Jean Darmette'
|
||||||
user.email = 'jean.darmette@triffouilis.fr'
|
user.email = 'jean.darmette@triffouilis.fr'
|
||||||
|
@ -78,7 +79,6 @@ def local_user():
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def admin_user():
|
def admin_user():
|
||||||
get_publisher().user_class.wipe()
|
|
||||||
user = get_publisher().user_class()
|
user = get_publisher().user_class()
|
||||||
user.name = 'John Doe Admin'
|
user.name = 'John Doe Admin'
|
||||||
user.email = 'john.doe@example.com'
|
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']
|
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()
|
pub.role_class.wipe()
|
||||||
role = pub.role_class(name='test')
|
role = pub.role_class(name='test')
|
||||||
role.store()
|
role.store()
|
||||||
|
@ -823,36 +824,56 @@ def test_api_access_restrict_to_anonymised_data(pub, local_user):
|
||||||
access.access_key = '12345'
|
access.access_key = '12345'
|
||||||
access.store()
|
access.store()
|
||||||
|
|
||||||
resp = get_app(pub).get(
|
app = get_app(pub)
|
||||||
sign_uri(
|
|
||||||
'/api/forms/test/list?full=on',
|
if http_basic_auth:
|
||||||
user=local_user,
|
# there's not "defaults to admin" permissions in case of basic authentication.
|
||||||
orig=access.access_identifier,
|
access.roles = [role]
|
||||||
key=access.access_key,
|
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 len(resp.json) == 10
|
||||||
assert resp.json[0]['fields']['foobar'] == 'FOO BAR1'
|
assert resp.json[0]['fields']['foobar'] == 'FOO BAR1'
|
||||||
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
|
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
|
||||||
assert resp.json[0].get('user')
|
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
|
# restrict API access to anonymised data
|
||||||
access.restrict_to_anonymised_data = True
|
access.restrict_to_anonymised_data = True
|
||||||
access.store()
|
access.store()
|
||||||
|
|
||||||
resp = get_app(pub).get(
|
resp = get_url('/api/forms/test/list?full=on')
|
||||||
sign_uri(
|
|
||||||
'/api/forms/test/list?full=on',
|
|
||||||
user=local_user,
|
|
||||||
orig=access.access_identifier,
|
|
||||||
key=access.access_key,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assert len(resp.json) == 10
|
assert len(resp.json) == 10
|
||||||
assert 'foobar' not in resp.json[0]['fields']
|
assert 'foobar' not in resp.json[0]['fields']
|
||||||
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
|
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
|
||||||
assert not resp.json[0].get('user')
|
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):
|
def test_api_geojson_formdata(pub, local_user):
|
||||||
pub.role_class.wipe()
|
pub.role_class.wipe()
|
||||||
|
|
24
wcs/api.py
24
wcs/api.py
|
@ -189,17 +189,19 @@ class ApiFormPageMixin:
|
||||||
raise TraversalError()
|
raise TraversalError()
|
||||||
|
|
||||||
def check_access(self, api_name=None):
|
def check_access(self, api_name=None):
|
||||||
if get_request().has_anonymised_data_api_restriction():
|
if get_request().user and get_request().user.is_admin:
|
||||||
if not is_url_signed() or (get_request().user and get_request().user.is_admin):
|
return # grant access to admins, to ease debug
|
||||||
raise AccessForbiddenError('user not authenticated')
|
|
||||||
else:
|
if get_request().has_anonymised_data_api_restriction() and is_url_signed():
|
||||||
if get_request().user and get_request().user.is_admin:
|
# when requesting anonymous data, a signature is enough
|
||||||
return # grant access to admins, to ease debug
|
return
|
||||||
api_user = get_user_from_api_query_string(api_name=api_name)
|
|
||||||
if not api_user:
|
api_user = get_user_from_api_query_string(api_name=api_name)
|
||||||
raise AccessForbiddenError('user not authenticated')
|
|
||||||
if not self.formdef.is_of_concern_for_user(api_user):
|
if not api_user:
|
||||||
raise AccessForbiddenError('unsufficient roles')
|
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):
|
def _q_lookup(self, component):
|
||||||
if component == 'ics':
|
if component == 'ics':
|
||||||
|
|
|
@ -89,6 +89,9 @@ class ApiAccess(XmlStorableObject):
|
||||||
is_api_user = True
|
is_api_user = True
|
||||||
anonymous = False
|
anonymous = False
|
||||||
|
|
||||||
|
def __init__(self, api_access):
|
||||||
|
self.api_access = api_access
|
||||||
|
|
||||||
def can_go_in_admin(self):
|
def can_go_in_admin(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -98,7 +101,7 @@ class ApiAccess(XmlStorableObject):
|
||||||
def get_roles(self):
|
def get_roles(self):
|
||||||
return self.roles
|
return self.roles
|
||||||
|
|
||||||
user = RestrictedApiUser()
|
user = RestrictedApiUser(self)
|
||||||
user.roles = [x.id for x in self.get_roles()]
|
user.roles = [x.id for x in self.get_roles()]
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
|
@ -158,13 +158,12 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
||||||
session = get_session()
|
session = get_session()
|
||||||
mine = False
|
mine = False
|
||||||
if api_call:
|
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):
|
if is_url_signed() or (get_request().user and get_request().user.is_admin):
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
raise errors.AccessUnauthorizedError()
|
raise errors.AccessUnauthorizedError()
|
||||||
else:
|
|
||||||
user = get_user_from_api_query_string() or get_request().user
|
|
||||||
else:
|
else:
|
||||||
user = get_request().user
|
user = get_request().user
|
||||||
if user and not user.anonymous:
|
if user and not user.anonymous:
|
||||||
|
|
|
@ -223,11 +223,16 @@ class HTTPRequest(quixote.http_request.HTTPRequest):
|
||||||
|
|
||||||
if 'anonymise' in self.form:
|
if 'anonymise' in self.form:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
orig = self.form.get('orig')
|
orig = self.form.get('orig')
|
||||||
if orig:
|
if orig:
|
||||||
api_access = ApiAccess.get_by_identifier(orig)
|
api_access = ApiAccess.get_by_identifier(orig)
|
||||||
if api_access:
|
if api_access:
|
||||||
return api_access.restrict_to_anonymised_data
|
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
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
Loading…
Reference in New Issue