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

This commit is contained in:
Frédéric Péters 2017-07-26 13:54:22 +02:00
parent 412844fbdd
commit fcfda2d576
4 changed files with 62 additions and 21 deletions

View File

@ -1445,7 +1445,8 @@ def test_api_geojson_formdata(pub, local_user):
formdef.store()
resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user), status=404)
def test_api_ics_formdata(pub, local_user):
@pytest.fixture
def ics_data(local_user):
Role.wipe()
role = Role(name='test')
role.store()
@ -1462,11 +1463,6 @@ def test_api_ics_formdata(pub, local_user):
data_class = formdef.data_class()
data_class.wipe()
# check access is denied if the user has not the appropriate role
resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar', user=local_user), status=403)
# even if there's an anonymse parameter
resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar?anonymise', user=local_user), status=403)
date = datetime.datetime(2014, 1, 20, 12, 00)
for i in range(30):
formdata = data_class()
@ -1479,6 +1475,14 @@ def test_api_ics_formdata(pub, local_user):
formdata.jump_status('finished')
formdata.store()
def test_api_ics_formdata(pub, local_user, ics_data):
role = Role.select()[0]
# check access is denied if the user has not the appropriate role
resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar', user=local_user), status=403)
# even if there's an anonymse parameter
resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar?anonymise', user=local_user), status=403)
# add proper role to user
local_user.roles = [role.id]
local_user.store()
@ -1495,6 +1499,36 @@ def test_api_ics_formdata(pub, local_user):
# check 404 on erroneous field var
resp = get_app(pub).get(sign_uri('/api/forms/test/ics/xxx', user=local_user), status=404)
def test_api_ics_formdata_http_auth(pub, local_user, ics_data):
role = Role.select()[0]
# no access
app = get_app(pub)
app.authorization = ('Basic', ('user', 'password'))
resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=403)
# add authentication info
pub.load_site_options()
pub.site_options.add_section('api-http-auth-ics')
pub.site_options.set('api-http-auth-ics', 'user', 'password')
pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
# check access is denied if the user has not the appropriate role
resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=403)
# add proper role to user
local_user.roles = [role.id]
local_user.store()
# check it gets the data
resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=200)
assert resp.headers['content-type'] == 'text/calendar; charset=utf-8'
assert resp.body.count('BEGIN:VEVENT') == 10
# check it fails with a different password
app.authorization = ('Basic', ('user', 'password2'))
resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=403)
def test_roles(pub, local_user):
Role.wipe()
role = Role(name='Hello World')

View File

@ -117,16 +117,13 @@ class ApiFormPage(BackofficeFormPage):
self.formdef = FormDef.get_by_urlname(component)
except KeyError:
raise TraversalError()
# check access for all paths, to block access to formdata that would
# otherwise be accessible if the user is the submitter.
self.check_access()
def check_access(self):
def check_access(self, api_name=None):
if 'anonymise' in get_request().form:
if not is_url_signed() or (get_request().user and get_request().user.is_admin):
raise AccessForbiddenError('user not authenticated')
else:
api_user = get_user_from_api_query_string()
api_user = get_user_from_api_query_string(api_name=api_name)
if not api_user:
if get_request().user and get_request().user.is_admin:
return # grant access to admins, to ease debug
@ -138,6 +135,9 @@ class ApiFormPage(BackofficeFormPage):
if component == 'ics':
return self.ics()
# check access for all paths, to block access to formdata that would
# otherwise be accessible if the user is the submitter.
self.check_access()
try:
formdata = self.formdef.data_class().get(component)
except KeyError:
@ -147,10 +147,6 @@ class ApiFormPage(BackofficeFormPage):
class ApiFormsDirectory(Directory):
def _q_lookup(self, component):
if not is_url_signed():
# grant access to admins, to ease debug
if not (get_request().user and get_request().user.is_admin):
raise AccessForbiddenError('user not authenticated')
return ApiFormPage(component)

View File

@ -98,10 +98,22 @@ def is_url_signed(utcnow=None, duration=DEFAULT_DURATION):
return True
def get_user_from_api_query_string():
if not is_url_signed():
def get_user_from_api_query_string(api_name=None):
auth_header = get_request().get_header('Authorization', '')
if auth_header and api_name:
if not auth_header.startswith('Basic '):
# we do not handle other authentication schemes
raise AccessForbiddenError('unhandled authorization header')
auth_header = auth_header.split(' ', 1)[1]
username, password = base64.decodestring(auth_header).split(':', 1)
configured_password = get_publisher().get_site_option(
username, section='api-http-auth-%s' % api_name)
if configured_password != password:
raise AccessForbiddenError('invalid authorization')
elif not is_url_signed():
return None
# Signature is good. Now looking for the user, by email/NameID.
# Signature or auth header are ok.
# Look for the user, by email/NameID.
user = None
if get_request().form.get('email'):
email = get_request().form.get('email')

View File

@ -1604,7 +1604,8 @@ class FormPage(Directory):
if 'anonymise' in get_request().form:
# api/ will let this pass but we don't want that.
raise errors.AccessForbiddenError()
self.check_access()
self.check_access('ics')
user = get_user_from_api_query_string('ics') or get_request().user
formdef = self.formdef
selected_filter = self.get_filter_from_query()
@ -1625,8 +1626,6 @@ class FormPage(Directory):
else:
raise errors.TraversalError()
user = get_user_from_api_query_string() or get_request().user
formdatas, total_count = FormDefUI(formdef).get_listing_items(
selected_filter, user=user, query=query, criterias=criterias)