diff --git a/awx/main/serializers.py b/awx/main/serializers.py index 0a86bf7cdc..970c7c17f2 100644 --- a/awx/main/serializers.py +++ b/awx/main/serializers.py @@ -3,6 +3,8 @@ # Python import json +import re +import socket import urlparse # PyYAML @@ -337,6 +339,31 @@ class HostSerializer(BaseSerializerWithVariables): d['groups'] = [{'id': g.id, 'name': g.name} for g in obj.groups.all()] return d + def validate_name(self, attrs, source): + name = unicode(attrs.get(source, '')) + # Allow hostname (except IPv6 for now) to specify the port # inline. + if name.count(':') == 1: + name, port = name.split(':') + try: + port = int(port) + if port < 1 or port > 65535: + raise ValueError + except ValueError: + raise serializers.ValidationError('Invalid port specification') + for family in (socket.AF_INET, socket.AF_INET6): + try: + socket.inet_pton(family, name) + return attrs + except socket.error: + pass + # Hostname should match the following regular expression and have at + # last one letter in the name (to catch invalid IPv4 addresses from + # above). + valid_host_re = r'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' + if re.match(valid_host_re, name) and re.match(r'^.*?[a-zA-Z].*?$', name): + return attrs + raise serializers.ValidationError('Invalid host name or IP') + class GroupSerializer(BaseSerializerWithVariables): class Meta: @@ -356,6 +383,12 @@ class GroupSerializer(BaseSerializerWithVariables): )) return res + def validate_name(self, attrs, source): + name = attrs.get(source, '') + if name in ('all', '_meta'): + raise serializers.ValidationError('Invalid group name') + return attrs + class GroupTreeSerializer(GroupSerializer): children = serializers.SerializerMethodField('get_children') diff --git a/awx/main/tests/inventory.py b/awx/main/tests/inventory.py index 8487157631..cf4b605b09 100644 --- a/awx/main/tests/inventory.py +++ b/awx/main/tests/inventory.py @@ -249,9 +249,9 @@ class InventoryTest(BaseTest): organization = self.organizations[0] ) invalid = dict(name='asdf0.example.com') - new_host_a = dict(name='asdf0.example.com', inventory=inv.pk) + new_host_a = dict(name='asdf0.example.com:1022', inventory=inv.pk) new_host_b = dict(name='asdf1.example.com', inventory=inv.pk) - new_host_c = dict(name='asdf2.example.com', inventory=inv.pk) + new_host_c = dict(name='127.1.2.3:2022', inventory=inv.pk) new_host_d = dict(name='asdf3.example.com', inventory=inv.pk) new_host_e = dict(name='asdf4.example.com', inventory=inv.pk) host_data0 = self.post(hosts, data=invalid, expect=400, auth=self.get_super_credentials()) @@ -296,6 +296,23 @@ class InventoryTest(BaseTest): self.assertEqual(Host.objects.get(id=host_data3['id']).variables, host_data3['variables']) self.assertEqual(Host.objects.get(id=host_data3['id']).variables_dict, {'angry': 'penguin'}) + # Try with invalid hostnames and invalid IPs. + data = dict(name='', inventory=inv.pk) + with self.current_user(self.super_django_user): + response = self.post(hosts, data=data, expect=400) + data = dict(name='not a valid host name', inventory=inv.pk) + with self.current_user(self.super_django_user): + response = self.post(hosts, data=data, expect=400) + data = dict(name='validhost:99999', inventory=inv.pk) + with self.current_user(self.super_django_user): + response = self.post(hosts, data=data, expect=400) + data = dict(name='123.234.345.456', inventory=inv.pk) + with self.current_user(self.super_django_user): + response = self.post(hosts, data=data, expect=400) + data = dict(name='2001::1::3F', inventory=inv.pk) + with self.current_user(self.super_django_user): + response = self.post(hosts, data=data, expect=400) + ########################################### # GROUPS @@ -328,6 +345,15 @@ class InventoryTest(BaseTest): # hostnames must be unique inside an organization group_data4 = self.post(groups, data=new_group_c, expect=400, auth=self.get_other_credentials()) + # Check that we don't allow creating reserved group names. + data = dict(name='all', inventory=inv.pk) + with self.current_user(self.super_django_user): + response = self.post(groups, data=data, expect=400) + data = dict(name='_meta', inventory=inv.pk) + with self.current_user(self.super_django_user): + response = self.post(groups, data=data, expect=400) + + ################################################# # HOSTS->inventories POST via subcollection