Skip to content

Commit 0cb7fae

Browse files
committed
fix(controller): validate config keys at the api level
1 parent 3838a0a commit 0cb7fae

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

controller/api/serializers.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
CPUSHARE_MATCH = re.compile(r'^(?P<cpu>[0-9]+)$')
2222
TAGKEY_MATCH = re.compile(r'^[a-z]+$')
2323
TAGVAL_MATCH = re.compile(r'^\w+$')
24+
CONFIGKEY_MATCH = re.compile(r'^[a-z_]+[a-z0-9_]*$', re.IGNORECASE)
2425

2526

2627
class JSONFieldSerializer(serializers.Field):
@@ -181,6 +182,14 @@ class Meta:
181182
"""Metadata options for a :class:`ConfigSerializer`."""
182183
model = models.Config
183184

185+
def validate_values(self, value):
186+
for k, v in value.viewitems():
187+
if not re.match(CONFIGKEY_MATCH, k):
188+
raise serializers.ValidationError(
189+
"Config keys must start with a letter or underscore and "
190+
"only contain [A-z0-9_]")
191+
return value
192+
184193
def validate_memory(self, value):
185194
for k, v in value.viewitems():
186195
if v is None: # use NoneType to unset a value

controller/api/tests/test_config.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,41 @@ def test_config_str(self):
257257
config = Config.objects.get(uuid=config5['uuid'])
258258
self.assertEqual(str(config), "{}-{}".format(config5['app'], config5['uuid'][:7]))
259259

260+
@mock.patch('requests.post', mock_status_ok)
261+
def test_valid_config_keys(self):
262+
"""Test that valid config keys are accepted.
263+
"""
264+
keys = ("FOO", "_foo", "f001", "FOO_BAR_BAZ_")
265+
url = '/v1/apps'
266+
response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
267+
self.assertEqual(response.status_code, 201)
268+
app_id = response.data['id']
269+
url = '/v1/apps/{app_id}/config'.format(**locals())
270+
for k in keys:
271+
body = {'values': json.dumps({k: "testvalue"})}
272+
resp = self.client.post(
273+
url, json.dumps(body), content_type='application/json',
274+
HTTP_AUTHORIZATION='token {}'.format(self.token))
275+
self.assertEqual(resp.status_code, 201)
276+
self.assertIn(k, resp.data['values'])
277+
278+
@mock.patch('requests.post', mock_status_ok)
279+
def test_invalid_config_keys(self):
280+
"""Test that invalid config keys are rejected.
281+
"""
282+
keys = ("123", "../../foo", "FOO/", "FOO-BAR")
283+
url = '/v1/apps'
284+
response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
285+
self.assertEqual(response.status_code, 201)
286+
app_id = response.data['id']
287+
url = '/v1/apps/{app_id}/config'.format(**locals())
288+
for k in keys:
289+
body = {'values': json.dumps({k: "testvalue"})}
290+
resp = self.client.post(
291+
url, json.dumps(body), content_type='application/json',
292+
HTTP_AUTHORIZATION='token {}'.format(self.token))
293+
self.assertEqual(resp.status_code, 400)
294+
260295
@mock.patch('requests.post', mock_status_ok)
261296
def test_admin_can_create_config_on_other_apps(self):
262297
"""If a non-admin creates an app, an administrator should be able to set config

0 commit comments

Comments
 (0)