Properly replace flask view args in links

When the API Prefix is used in a Flask API, it is possible the flask
view argument specification will bleed through to the self link instead
of a properly formated url.

The add_self_reference_links mechanism in keystone.server.flask.common
now substitutes out the self link to the {} substitution and applies
a .format() utilizing the view args to the URI in the self link.

Change-Id: Ic5c89c285ed964de7411b273567bb97fcf43da06
closes-bug: #1794552
This commit is contained in:
Morgan Fainberg 2018-09-28 13:08:37 -07:00
parent 30bd48c205
commit 1efecc92c0
3 changed files with 41 additions and 0 deletions

View File

@ -676,6 +676,14 @@ class ResourceBase(flask_restful.Resource):
collection_element = collection_name or cls.collection_key
if cls.api_prefix:
api_prefix = cls.api_prefix.lstrip('/').rstrip('/')
# ensure we have substituted the flask-arg specification
# to the "keystone" mechanism, then format the string
api_prefix = _URL_SUBST.sub('{\\1}', api_prefix)
if flask.request.view_args:
# if a prefix has substitutions it is *required* that the
# values are passed as view_args to the HTTP action method
# (e.g. head/get/post/...).
api_prefix = api_prefix.format(**flask.request.view_args)
collection_element = '/'.join(
[api_prefix, collection_name or cls.collection_key])
self_link = base_url(path='/'.join([collection_element, ref['id']]))

View File

@ -884,6 +884,8 @@ class TestCase(BaseTestCase):
app.register_error_handler(404, page_not_found_teapot)
self.test_client = app.test_client
self.test_request_context = app.test_request_context
self.cleanup_instance('test_request_context')
self.cleanup_instance('test_client')
return keystone_flask.setup_app_middleware(app)

View File

@ -610,3 +610,34 @@ class TestKeystoneFlaskCommon(rest.RestfulTestCase):
self.assertRaises(exception.ValidationError,
flask_common.ResourceBase._normalize_domain_id,
ref=ref_without_domain_id)
def test_api_prefix_self_referential_link_substitution(self):
view_arg = uuid.uuid4().hex
class TestResource(flask_common.ResourceBase):
api_prefix = '/<string:test_value>/nothing'
# use a dummy request context, no enforcement is happening
# therefore we don't need the heavy lifting of a full request
# run.
with self.test_request_context(
path='/%s/nothing/values' % view_arg,
base_url='https://localhost/'):
# explicitly set the view_args, this is a special case
# for a synthetic test case, usually one would rely on
# a full request stack to set these.
flask.request.view_args = {'test_value': view_arg}
# create dummy ref
ref = {'id': uuid.uuid4().hex}
# add the self referential link
TestResource._add_self_referential_link(
ref, collection_name='values')
# Check that the link in fact starts with what we expect
# including the explicit view arg.
self.assertTrue(ref['links']['self'].startswith(
'https://localhost/v3/%s' % view_arg)
)