From 871e575706fd0f33290d2c63dcd403653c5ce45e Mon Sep 17 00:00:00 2001 From: Benjamin Bertrand <benjamin.bertrand@esss.se> Date: Tue, 22 May 2018 16:00:41 +0200 Subject: [PATCH] Add tag and device type validation Tags and device_types are used as key in Ansible roles. They shouldn't contain any spaces to avoid issues. JIRA INFRA-334 --- app/defaults.py | 4 +-- app/models.py | 19 ++++++++++++- app/validators.py | 2 ++ ...05c0c835_remove_spaces_from_device_type.py | 28 +++++++++++++++++++ tests/functional/conftest.py | 1 + tests/functional/factories.py | 9 ++++++ tests/functional/test_models.py | 16 +++++++++++ 7 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 migrations/versions/f5a605c0c835_remove_spaces_from_device_type.py diff --git a/app/defaults.py b/app/defaults.py index afc5efa..737f7d6 100644 --- a/app/defaults.py +++ b/app/defaults.py @@ -21,8 +21,8 @@ defaults = [ models.Action(name='Set as parent'), models.Action(name='Update'), - models.DeviceType(name='Physical Machine'), - models.DeviceType(name='Virtual Machine'), + models.DeviceType(name='PhysicalMachine'), + models.DeviceType(name='VirtualMachine'), models.DeviceType(name='Network'), models.DeviceType(name='MicroTCA'), models.DeviceType(name='VME'), diff --git a/app/models.py b/app/models.py index ceb3e77..dc1cf05 100644 --- a/app/models.py +++ b/app/models.py @@ -23,7 +23,8 @@ from flask_login import UserMixin from wtforms import ValidationError from .extensions import db, login_manager, ldap_manager, cache from .plugins import FlaskUserPlugin -from .validators import ICS_ID_RE, HOST_NAME_RE, VLAN_NAME_RE, MAC_ADDRESS_RE +from .validators import (ICS_ID_RE, HOST_NAME_RE, VLAN_NAME_RE, MAC_ADDRESS_RE, + DEVICE_TYPE_RE, TAG_RE) from . import utils @@ -594,6 +595,14 @@ interfacetags_table = db.Table( class Tag(QRCodeMixin, db.Model): admin_only = db.Column(db.Boolean, nullable=False, default=False) + @validates('name') + def validate_name(self, key, string): + """Ensure the name field matches the required format""" + if string is not None: + if TAG_RE.fullmatch(string) is None: + raise ValidationError(f"'{string}' is an invalid tag name") + return string + class DeviceType(db.Model): __tablename__ = 'device_type' @@ -602,6 +611,14 @@ class DeviceType(db.Model): hosts = db.relationship('Host', backref='device_type') + @validates('name') + def validate_name(self, key, string): + """Ensure the name field matches the required format""" + if string is not None: + if DEVICE_TYPE_RE.fullmatch(string) is None: + raise ValidationError(f"'{string}' is an invalid device type name") + return string + def __str__(self): return self.name diff --git a/app/validators.py b/app/validators.py index c73c3d9..1228f50 100644 --- a/app/validators.py +++ b/app/validators.py @@ -18,6 +18,8 @@ ICS_ID_RE = re.compile('[A-Z]{3}[0-9]{3}') HOST_NAME_RE = re.compile('^[a-z0-9\-]{2,20}$') VLAN_NAME_RE = re.compile('^[A-Za-z0-9\-]{3,25}$') MAC_ADDRESS_RE = re.compile('^(?:[0-9a-fA-F]{2}[:-]?){5}[0-9a-fA-F]{2}$') +DEVICE_TYPE_RE = re.compile('^[A-Za-z0-9]{3,25}$') +TAG_RE = DEVICE_TYPE_RE class NoValidateSelectField(SelectField): diff --git a/migrations/versions/f5a605c0c835_remove_spaces_from_device_type.py b/migrations/versions/f5a605c0c835_remove_spaces_from_device_type.py new file mode 100644 index 0000000..6373968 --- /dev/null +++ b/migrations/versions/f5a605c0c835_remove_spaces_from_device_type.py @@ -0,0 +1,28 @@ +"""remove spaces from device_type + +Revision ID: f5a605c0c835 +Revises: ea606be23b95 +Create Date: 2018-05-22 13:41:28.137611 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f5a605c0c835' +down_revision = 'ea606be23b95' +branch_labels = None +depends_on = None + + +def upgrade(): + device_type = sa.sql.table('device_type', sa.sql.column('id'), sa.sql.column('name')) + op.execute(device_type.update().where(device_type.c.name == 'Physical Machine').values(name='PhysicalMachine')) + op.execute(device_type.update().where(device_type.c.name == 'Virtual Machine').values(name='VirtualMachine')) + + +def downgrade(): + device_type = sa.sql.table('device_type', sa.sql.column('id'), sa.sql.column('name')) + op.execute(device_type.update().where(device_type.c.name == 'PhysicalMachine').values(name='Physical Machine')) + op.execute(device_type.update().where(device_type.c.name == 'VirtualMachine').values(name='Virtual Machine')) diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 3b69217..c0d6e2c 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -32,6 +32,7 @@ register(factories.HostFactory) register(factories.MacFactory) register(factories.DomainFactory) register(factories.CnameFactory) +register(factories.TagFactory) @pytest.fixture(scope='session') diff --git a/tests/functional/factories.py b/tests/functional/factories.py index 787fbdc..50de310 100644 --- a/tests/functional/factories.py +++ b/tests/functional/factories.py @@ -189,3 +189,12 @@ class CnameFactory(factory.alchemy.SQLAlchemyModelFactory): name = factory.Sequence(lambda n: f'host{n}') interface = factory.SubFactory(InterfaceFactory) user = factory.SubFactory(UserFactory) + + +class TagFactory(factory.alchemy.SQLAlchemyModelFactory): + class Meta: + model = models.Tag + sqlalchemy_session = common.Session + sqlalchemy_session_persistence = 'commit' + + name = factory.Sequence(lambda n: f'Tag{n}') diff --git a/tests/functional/test_models.py b/tests/functional/test_models.py index bb84d7c..fd5063d 100644 --- a/tests/functional/test_models.py +++ b/tests/functional/test_models.py @@ -121,3 +121,19 @@ def test_manufacturer_favorite_users(user_factory, manufacturer_factory): assert user2 in manufacturer2.favorite_users assert user2 not in manufacturer1.favorite_users assert user3 in manufacturer1.favorite_users + + +def test_device_type_validation(device_type_factory): + device_type = device_type_factory(name='PhysicalMachine') + assert device_type.name == 'PhysicalMachine' + with pytest.raises(ValidationError) as excinfo: + device_type = device_type_factory(name='Physical Machine') + assert "'Physical Machine' is an invalid device type name" in str(excinfo.value) + + +def test_tag_validation(tag_factory): + tag = tag_factory(name='IOC') + assert tag.name == 'IOC' + with pytest.raises(ValidationError) as excinfo: + tag = tag_factory(name='My tag') + assert "'My tag' is an invalid tag name" in str(excinfo.value) -- GitLab