From 44c675db872cb6c814c2b9ace07d8dd7196c47d1 Mon Sep 17 00:00:00 2001 From: Daniel Mursa Date: Mon, 16 Dec 2024 10:34:18 +0100 Subject: [PATCH] [#485] Merge closed branch --- docker/setup_configuration/data.yaml | 17 + .../tests/files/token_auth_invalid_setup.yaml | 3 + .../token_auth_valid_setup_complete.yaml | 20 + .../files/token_auth_valid_setup_default.yaml | 12 + .../tests/test_token_auth_config.py | 369 ++++++++++++++++++ src/objects/tests/commands/__init__.py | 0 src/objects/tests/config/__init__.py | 0 src/objects/token/admin.py | 2 +- ...enauth_identifier_alter_tokenauth_token.py | 70 ++++ src/objects/token/models.py | 12 +- src/objects/token/tests/factories.py | 1 + src/objects/token/tests/test_migrations.py | 88 +++++ src/objects/token/tests/test_validators.py | 54 +++ src/objects/token/validators.py | 20 + 14 files changed, 666 insertions(+), 2 deletions(-) create mode 100644 src/objects/setup_configuration/tests/files/token_auth_invalid_setup.yaml create mode 100644 src/objects/setup_configuration/tests/files/token_auth_valid_setup_complete.yaml create mode 100644 src/objects/setup_configuration/tests/files/token_auth_valid_setup_default.yaml create mode 100644 src/objects/setup_configuration/tests/test_token_auth_config.py delete mode 100644 src/objects/tests/commands/__init__.py delete mode 100644 src/objects/tests/config/__init__.py create mode 100644 src/objects/token/migrations/0017_tokenauth_identifier_alter_tokenauth_token.py create mode 100644 src/objects/token/tests/test_migrations.py create mode 100644 src/objects/token/tests/test_validators.py create mode 100644 src/objects/token/validators.py diff --git a/docker/setup_configuration/data.yaml b/docker/setup_configuration/data.yaml index ed42429d..2e20b590 100644 --- a/docker/setup_configuration/data.yaml +++ b/docker/setup_configuration/data.yaml @@ -1,3 +1,20 @@ +token_tokenauth_config_enable: true +token_tokenauth: + items: + - identifier: token-1 + token: 18b2b74ef994314b84021d47b9422e82b685d82f + contact_person: Person 1 + email: person-1@example.com + organization: Organization 1 + application: Application 1 + administration: Administration 1 + +sites_config_enable: true +sites_config: + items: + - domain: example.com + name: Example site + zgw_consumers_config_enable: true zgw_consumers: services: diff --git a/src/objects/setup_configuration/tests/files/token_auth_invalid_setup.yaml b/src/objects/setup_configuration/tests/files/token_auth_invalid_setup.yaml new file mode 100644 index 00000000..2749f17a --- /dev/null +++ b/src/objects/setup_configuration/tests/files/token_auth_invalid_setup.yaml @@ -0,0 +1,3 @@ +token_tokenauth_config_enable: true +token_tokenauth: + items: \ No newline at end of file diff --git a/src/objects/setup_configuration/tests/files/token_auth_valid_setup_complete.yaml b/src/objects/setup_configuration/tests/files/token_auth_valid_setup_complete.yaml new file mode 100644 index 00000000..5f91577c --- /dev/null +++ b/src/objects/setup_configuration/tests/files/token_auth_valid_setup_complete.yaml @@ -0,0 +1,20 @@ +token_tokenauth_config_enable: true +token_tokenauth: + items: + - identifier: token-1 + token: 18b2b74ef994314b84021d47b9422e82b685d82f + contact_person: Person 1 + email: person-1@example.com + organization: Organization 1 + application: Application 1 + administration: Administration 1 + is_superuser: True + + - identifier: token-2 + token: e882642bd0ec2482adcdc97258c2e6f98cb06d85 + contact_person: Person 2 + email: person-2@example.com + organization: Organization 2 + application: Application 2 + administration: Administration 2 + is_superuser: True diff --git a/src/objects/setup_configuration/tests/files/token_auth_valid_setup_default.yaml b/src/objects/setup_configuration/tests/files/token_auth_valid_setup_default.yaml new file mode 100644 index 00000000..b9af9475 --- /dev/null +++ b/src/objects/setup_configuration/tests/files/token_auth_valid_setup_default.yaml @@ -0,0 +1,12 @@ +token_tokenauth_config_enable: true +token_tokenauth: + items: + - identifier: token-1 + token: 18b2b74ef994314b84021d47b9422e82b685d82f + contact_person: Person 1 + email: person-1@example.com + + - identifier: token-2 + token: e882642bd0ec2482adcdc97258c2e6f98cb06d85 + contact_person: Person 2 + email: person-2@example.com diff --git a/src/objects/setup_configuration/tests/test_token_auth_config.py b/src/objects/setup_configuration/tests/test_token_auth_config.py new file mode 100644 index 00000000..ba94d732 --- /dev/null +++ b/src/objects/setup_configuration/tests/test_token_auth_config.py @@ -0,0 +1,369 @@ +from pathlib import Path + +from django.test import TestCase + +from django_setup_configuration.exceptions import ( + ConfigurationRunFailed, + PrerequisiteFailed, +) +from django_setup_configuration.test_utils import execute_single_step + +from objects.setup_configuration.steps.token_auth import TokenAuthConfigurationStep +from objects.token.models import TokenAuth +from objects.token.tests.factories import TokenAuthFactory + +DIR_FILES = (Path(__file__).parent / "files").resolve() + + +class TokenAuthConfigurationStepTests(TestCase): + def test_valid_setup_default(self): + execute_single_step( + TokenAuthConfigurationStep, + yaml_source=str(DIR_FILES / "token_auth_valid_setup_default.yaml"), + ) + + tokens = TokenAuth.objects.all() + self.assertEqual(tokens.count(), 2) + + token = tokens.get(identifier="token-1") + self.assertEqual(token.token, "18b2b74ef994314b84021d47b9422e82b685d82f") + self.assertEqual(token.contact_person, "Person 1") + self.assertEqual(token.email, "person-1@example.com") + self.assertEqual(token.organization, "") + self.assertEqual(token.application, "") + self.assertEqual(token.administration, "") + self.assertFalse(token.is_superuser) + + token = tokens.get(identifier="token-2") + self.assertEqual(token.contact_person, "Person 2") + self.assertEqual(token.token, "e882642bd0ec2482adcdc97258c2e6f98cb06d85") + self.assertEqual(token.email, "person-2@example.com") + self.assertEqual(token.organization, "") + self.assertEqual(token.application, "") + self.assertEqual(token.administration, "") + self.assertFalse(token.is_superuser) + + def test_valid_setup_complete(self): + execute_single_step( + TokenAuthConfigurationStep, + yaml_source=str(DIR_FILES / "token_auth_valid_setup_complete.yaml"), + ) + + tokens = TokenAuth.objects.all() + self.assertEqual(tokens.count(), 2) + + # Same as configuration + token = tokens.get(identifier="token-1") + self.assertEqual(token.token, "18b2b74ef994314b84021d47b9422e82b685d82f") + self.assertEqual(token.contact_person, "Person 1") + self.assertEqual(token.email, "person-1@example.com") + self.assertEqual(token.organization, "Organization 1") + self.assertEqual(token.application, "Application 1") + self.assertEqual(token.administration, "Administration 1") + self.assertTrue(token.is_superuser) + + # Token data updated + token = tokens.get(identifier="token-2") + self.assertEqual(token.contact_person, "Person 2") + self.assertEqual(token.token, "e882642bd0ec2482adcdc97258c2e6f98cb06d85") + self.assertEqual(token.email, "person-2@example.com") + self.assertEqual(token.organization, "Organization 2") + self.assertEqual(token.application, "Application 2") + self.assertEqual(token.administration, "Administration 2") + self.assertTrue(token.is_superuser) + + def test_valid_update_existing_tokens(self): + TokenAuthFactory( + identifier="token-1", + token="18b2b74ef994314b84021d47b9422e82b685d82f", + contact_person="Person 1", + email="person-1@example.com", + organization="Organization XYZ", + application="Application XYZ", + administration="Administration XYZ", + ) + + TokenAuthFactory( + identifier="token-2", + token="1cad42916dfa439af8c69000bf7b6af6a66782af", + contact_person="Person 3", + email="person-3@example.com", + ) + execute_single_step( + TokenAuthConfigurationStep, + yaml_source=str(DIR_FILES / "token_auth_valid_setup_complete.yaml"), + ) + + tokens = TokenAuth.objects.all() + self.assertEqual(tokens.count(), 2) + + # Same as configuration + token = tokens.get(identifier="token-1") + self.assertEqual(token.token, "18b2b74ef994314b84021d47b9422e82b685d82f") + self.assertEqual(token.contact_person, "Person 1") + self.assertEqual(token.email, "person-1@example.com") + self.assertEqual(token.organization, "Organization 1") + self.assertEqual(token.application, "Application 1") + self.assertEqual(token.administration, "Administration 1") + self.assertTrue(token.is_superuser) + + # Token data updated + token = tokens.get(identifier="token-2") + self.assertEqual(token.contact_person, "Person 2") + self.assertEqual(token.token, "e882642bd0ec2482adcdc97258c2e6f98cb06d85") + self.assertEqual(token.email, "person-2@example.com") + self.assertEqual(token.organization, "Organization 2") + self.assertEqual(token.application, "Application 2") + self.assertEqual(token.administration, "Administration 2") + self.assertTrue(token.is_superuser) + + self.assertNotEqual(token.token, "1cad42916dfa439af8c69000bf7b6af6a66782af") + self.assertNotEqual(token.contact_person, "Person 3") + self.assertNotEqual(token.email, "person-3@example.com") + + def test_valid_idempotent_step(self): + execute_single_step( + TokenAuthConfigurationStep, + yaml_source=str(DIR_FILES / "token_auth_valid_setup_complete.yaml"), + ) + + tokens = TokenAuth.objects.all() + self.assertEqual(tokens.count(), 2) + + old_token_a = tokens.get(identifier="token-1") + self.assertEqual(old_token_a.identifier, "token-1") + self.assertEqual(old_token_a.token, "18b2b74ef994314b84021d47b9422e82b685d82f") + self.assertEqual(old_token_a.contact_person, "Person 1") + self.assertEqual(old_token_a.email, "person-1@example.com") + self.assertEqual(old_token_a.organization, "Organization 1") + self.assertEqual(old_token_a.application, "Application 1") + self.assertEqual(old_token_a.administration, "Administration 1") + self.assertTrue(old_token_a.is_superuser) + + old_token_b = tokens.get(identifier="token-2") + self.assertEqual(old_token_b.identifier, "token-2") + self.assertEqual(old_token_b.contact_person, "Person 2") + self.assertEqual(old_token_b.token, "e882642bd0ec2482adcdc97258c2e6f98cb06d85") + self.assertEqual(old_token_b.email, "person-2@example.com") + self.assertEqual(old_token_b.organization, "Organization 2") + self.assertEqual(old_token_b.application, "Application 2") + self.assertEqual(old_token_b.administration, "Administration 2") + self.assertTrue(old_token_b.is_superuser) + + execute_single_step( + TokenAuthConfigurationStep, + yaml_source=str(DIR_FILES / "token_auth_valid_setup_complete.yaml"), + ) + + tokens = TokenAuth.objects.all() + self.assertEqual(tokens.count(), 2) + + new_token_a = tokens.get(identifier="token-1") + self.assertEqual(new_token_a.identifier, old_token_a.identifier) + self.assertEqual(new_token_a.token, old_token_a.token) + self.assertEqual(new_token_a.contact_person, old_token_a.contact_person) + self.assertEqual(new_token_a.email, old_token_a.email) + self.assertEqual(new_token_a.organization, old_token_a.organization) + self.assertEqual(new_token_a.application, old_token_a.application) + self.assertEqual(new_token_a.administration, old_token_a.administration) + + new_token_b = tokens.get(identifier="token-2") + self.assertEqual(new_token_b.identifier, old_token_b.identifier) + self.assertEqual(new_token_b.contact_person, old_token_b.contact_person) + self.assertEqual(new_token_b.token, old_token_b.token) + self.assertEqual(new_token_b.email, old_token_b.email) + self.assertEqual(new_token_b.organization, old_token_b.organization) + self.assertEqual(new_token_b.application, old_token_b.application) + self.assertEqual(new_token_b.administration, old_token_b.administration) + + def test_invalid_setup(self): + with self.assertRaises(PrerequisiteFailed) as command_error: + execute_single_step( + TokenAuthConfigurationStep, + yaml_source=str(DIR_FILES / "token_auth_invalid_setup.yaml"), + ) + + self.assertTrue("Input should be a valid list" in str(command_error.exception)) + self.assertEqual(TokenAuth.objects.count(), 0) + + def test_invalid_setup_email(self): + object_source = { + "token_tokenauth_config_enable": True, + "token_tokenauth": { + "items": [ + { + "identifier": "token-1", + "token": "ba9d233e95e04c4a8a661a27daffe7c9bd019067", + "contact_person": "Person 1", + "email": "invalid", + "organization": "Organization 1", + "application": "Application 1", + "administration": "Administration 1", + }, + ], + }, + } + with self.assertRaises(ConfigurationRunFailed) as command_error: + execute_single_step(TokenAuthConfigurationStep, object_source=object_source) + + self.assertTrue( + "Validation error(s) occured for token-1" in str(command_error.exception) + ) + self.assertEqual(TokenAuth.objects.count(), 0) + + def test_invalid_setup_token(self): + object_source = { + "token_tokenauth_config_enable": True, + "token_tokenauth": { + "items": [ + { + "identifier": "token-1", + "token": "invalid token", + "contact_person": "Person 1", + "email": "person-1@example.com", + "organization": "Organization 1", + "application": "Application 1", + "administration": "Administration 1", + }, + ], + }, + } + with self.assertRaises(ConfigurationRunFailed) as command_error: + execute_single_step(TokenAuthConfigurationStep, object_source=object_source) + + self.assertTrue( + "Validation error(s) occured for token-1" in str(command_error.exception) + ) + self.assertEqual(TokenAuth.objects.count(), 0) + + def test_invalid_empty_token(self): + object_source = { + "token_tokenauth_config_enable": True, + "token_tokenauth": { + "items": [ + { + "identifier": "token-1", + "token": "", + "contact_person": "Person 1", + "email": "person-1@example.com", + "organization": "Organization 1", + "application": "Application 1", + "administration": "Administration 1", + }, + ], + }, + } + with self.assertRaises(ConfigurationRunFailed) as command_error: + execute_single_step(TokenAuthConfigurationStep, object_source=object_source) + + self.assertTrue( + "Validation error(s) occured for token-1" in str(command_error.exception) + ) + self.assertEqual(TokenAuth.objects.count(), 0) + + def test_invalid_setup_token_missing(self): + object_source = { + "token_tokenauth_config_enable": True, + "token_tokenauth": { + "items": [ + { + "identifier": "token-1", + "contact_person": "Person 1", + "email": "person-1@example.com", + "organization": "Organization 1", + "application": "Application 1", + "administration": "Administration 1", + }, + ], + }, + } + with self.assertRaises(PrerequisiteFailed) as command_error: + execute_single_step(TokenAuthConfigurationStep, object_source=object_source) + + self.assertTrue("Field required" in str(command_error.exception)) + self.assertEqual(TokenAuth.objects.count(), 0) + + def test_invalid_setup_token_unique(self): + object_source = { + "token_tokenauth_config_enable": True, + "token_tokenauth": { + "items": [ + { + "identifier": "token-1", + "contact_person": "Person 1", + "token": "ba9d233e95e04c4a8a661a27daffe7c9bd019067", + "email": "person-1@example.com", + "organization": "Organization 1", + "application": "Application 1", + "administration": "Administration 1", + }, + { + "identifier": "token-2", + "contact_person": "Person 2", + "token": "ba9d233e95e04c4a8a661a27daffe7c9bd019067", + "email": "person-2@example.com", + "organization": "Organization 2", + "application": "Application 2", + "administration": "Administration 2", + }, + ], + }, + } + with self.assertRaises(ConfigurationRunFailed) as command_error: + execute_single_step(TokenAuthConfigurationStep, object_source=object_source) + + self.assertTrue( + "Failed configuring token token-2" in str(command_error.exception) + ) + self.assertEqual(TokenAuth.objects.count(), 1) + + def test_invalid_setup_contact_person(self): + object_source = { + "token_tokenauth_config_enable": True, + "token_tokenauth": { + "items": [ + { + "identifier": "token-1", + "token": "ba9d233e95e04c4a8a661a27daffe7c9bd019067", + "contact_person": "", + "email": "person-1@example.com", + "organization": "Organization 1", + "application": "Application 1", + "administration": "Administration 1", + }, + ], + }, + } + with self.assertRaises(ConfigurationRunFailed) as command_error: + execute_single_step(TokenAuthConfigurationStep, object_source=object_source) + + self.assertTrue( + "Validation error(s) occured for token-1" in str(command_error.exception) + ) + self.assertEqual(TokenAuth.objects.count(), 0) + + def test_invalid_setup_identifier(self): + object_source = { + "token_tokenauth_config_enable": True, + "token_tokenauth": { + "items": [ + { + "identifier": "invalid identifier", + "token": "ba9d233e95e04c4a8a661a27daffe7c9bd019067", + "contact_person": "Person 1", + "email": "person-1@example.com", + "organization": "Organization 1", + "application": "Application 1", + "administration": "Administration 1", + }, + ], + }, + } + with self.assertRaises(ConfigurationRunFailed) as command_error: + execute_single_step(TokenAuthConfigurationStep, object_source=object_source) + + self.assertTrue( + "Validation error(s) occured for invalid identifier" + in str(command_error.exception) + ) + self.assertEqual(TokenAuth.objects.count(), 0) diff --git a/src/objects/tests/commands/__init__.py b/src/objects/tests/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/objects/tests/config/__init__.py b/src/objects/tests/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/objects/token/admin.py b/src/objects/token/admin.py index 3d7d82b6..7898b30d 100644 --- a/src/objects/token/admin.py +++ b/src/objects/token/admin.py @@ -162,7 +162,7 @@ def get_uuid(self, obj): @admin.register(TokenAuth) class TokenAuthAdmin(admin.ModelAdmin): list_display = ( - "token", + "identifier", "contact_person", "organization", "administration", diff --git a/src/objects/token/migrations/0017_tokenauth_identifier_alter_tokenauth_token.py b/src/objects/token/migrations/0017_tokenauth_identifier_alter_tokenauth_token.py new file mode 100644 index 00000000..8acb4211 --- /dev/null +++ b/src/objects/token/migrations/0017_tokenauth_identifier_alter_tokenauth_token.py @@ -0,0 +1,70 @@ +# Generated by Django 4.2.15 on 2024-12-11 10:07 +import logging + +from django.db import migrations, models +from django.db.migrations.state import StateApps + +import objects.token.validators + +logger = logging.getLogger(__name__) + + +def _generate_unique_identifiers(apps: StateApps, schema_editor) -> None: + TokenAuth = apps.get_model("token", "TokenAuth") + + count = 1 + + for token in TokenAuth.objects.filter(identifier__isnull=True): + while TokenAuth.objects.filter(identifier=f"token-{count}").exists(): + count += 1 + + identifier = f"token-{count}" + logger.debug(f"Generated {identifier} for token {token.pk}") + + token.identifier = identifier + token.save(update_fields=("identifier",)) + + +class Migration(migrations.Migration): + + dependencies = [ + ("token", "0016_alter_permission_token_auth"), + ] + + operations = [ + migrations.AddField( + model_name="tokenauth", + name="identifier", + field=models.CharField( + blank=True, + null=True, + ), + ), + migrations.RunPython( + code=_generate_unique_identifiers, + reverse_code=migrations.RunPython.noop, + ), + migrations.AlterField( + model_name="tokenauth", + name="identifier", + field=models.SlugField(unique=True), + ), + migrations.AlterField( + model_name="tokenauth", + name="identifier", + field=models.SlugField( + help_text="A human-friendly label to refer to this token", + unique=True, + ), + ), + migrations.AlterField( + model_name="tokenauth", + name="token", + field=models.CharField( + max_length=40, + unique=True, + validators=[objects.token.validators.validate_no_empty, objects.token.validators.validate_no_whitespace], + verbose_name="token", + ), + ), + ] diff --git a/src/objects/token/models.py b/src/objects/token/models.py index a11a7b2e..a0a12d99 100644 --- a/src/objects/token/models.py +++ b/src/objects/token/models.py @@ -6,12 +6,22 @@ from django.utils.translation import gettext_lazy as _ from objects.core.models import ObjectType +from objects.token.validators import validate_no_whitespace, validate_no_empty from .constants import PermissionModes class TokenAuth(models.Model): - token = models.CharField(_("token"), max_length=40, unique=True) + identifier = models.SlugField( + unique=True, + help_text=_("A human-friendly label to refer to this token"), + ) + token = models.CharField( + _("token"), + max_length=40, + unique=True, + validators=[validate_no_empty, validate_no_whitespace], + ) contact_person = models.CharField( _("contact person"), max_length=200, diff --git a/src/objects/token/tests/factories.py b/src/objects/token/tests/factories.py index 35246763..0baf0d32 100644 --- a/src/objects/token/tests/factories.py +++ b/src/objects/token/tests/factories.py @@ -7,6 +7,7 @@ class TokenAuthFactory(factory.django.DjangoModelFactory): + identifier = factory.Sequence(lambda sequence: f"token-{sequence}") contact_person = factory.Faker("name") email = factory.Faker("email") diff --git a/src/objects/token/tests/test_migrations.py b/src/objects/token/tests/test_migrations.py new file mode 100644 index 00000000..f4834931 --- /dev/null +++ b/src/objects/token/tests/test_migrations.py @@ -0,0 +1,88 @@ +from django.core.management import call_command +from django.db import connection +from django.db.migrations.executor import MigrationExecutor +from django.db.migrations.state import StateApps +from django.test import TransactionTestCase + + +class BaseMigrationTest(TransactionTestCase): + app: str + migrate_from: str # The migration before the one we want to test + migrate_to: str # The migration we want to test + + setting_overrides: dict = {} + + old_app_state: StateApps + app_state: StateApps + + def setUp(self) -> None: + """ + Setup the migration test by reversing to `migrate_from` state, + then applying the `migrate_to` state. + """ + assert self.app is not None, "You must define the `app` attribute" + assert self.migrate_from is not None, "You must define `migrate_from`" + assert self.migrate_to is not None, "You must define `migrate_to`" + + # Step 1: Set up the MigrationExecutor + executor = MigrationExecutor(connection) + + # Step 2: Reverse to the starting migration state + migrate_from = [(self.app, self.migrate_from)] + old_migrate_state = executor.migrate(migrate_from) + + self.old_app_state = old_migrate_state.apps + + def _perform_migration(self) -> None: + migrate_to = [(self.app, self.migrate_to)] + + executor = MigrationExecutor(connection) + executor.loader.build_graph() # reload the graph in case of dependency changes + executor.migrate(migrate_to) + + self.apps = executor.loader.project_state(migrate_to).apps + + @classmethod + def tearDownClass(cls) -> None: + super().tearDownClass() + + # reset to latest migration + call_command("migrate", verbosity=0, database=connection._alias) + + +class TestTokenAuthUniqueness(BaseMigrationTest): + app = "token" + migrate_from = "0016_alter_permission_token_auth" + migrate_to = "0017_tokenauth_identifier_alter_tokenauth_token" + + def test_migrate_tokens_check_attr(self): + TokenAuth = self.old_app_state.get_model("token", "TokenAuth") + self.assertFalse(hasattr(TokenAuth, "identifier")) + + self._perform_migration() + + TokenAuth = self.apps.get_model("token", "TokenAuth") + self.assertTrue(hasattr(TokenAuth, "identifier")) + + def test_migrate_tokens_to_unique_identifiers(self): + TokenAuth = self.old_app_state.get_model("token", "TokenAuth") + TokenAuth.objects.create( + token="aa018d1c576c9dae33be1e549f739f2834ebc811", + contact_person="Person 1", + email="test@example.com", + ) + TokenAuth.objects.create( + token="ab700d6bf906c2b4b42a961c529657314c6a8246", + contact_person="Other person", + email="somebody@else.com", + ) + + self._perform_migration() + + TokenAuth = self.apps.get_model("token", "TokenAuth") + tokens = TokenAuth.objects.all() + self.assertEqual(tokens.count(), 2) + + first_token = tokens.get(token="aa018d1c576c9dae33be1e549f739f2834ebc811") + second_token = tokens.get(token="ab700d6bf906c2b4b42a961c529657314c6a8246") + self.assertNotEqual(first_token.identifier, second_token.identifier) diff --git a/src/objects/token/tests/test_validators.py b/src/objects/token/tests/test_validators.py new file mode 100644 index 00000000..62e5d1ab --- /dev/null +++ b/src/objects/token/tests/test_validators.py @@ -0,0 +1,54 @@ +from django.core.exceptions import ValidationError +from django.test import SimpleTestCase + +from objects.token.validators import validate_no_empty, validate_no_whitespace + + +class NoEmptyValidatorTestCase(SimpleTestCase): + def test_valid(self): + self.assertIsNone(validate_no_empty("test123")) + + def test_invalid_string(self): + with self.assertRaises(ValidationError): + validate_no_empty("") + + def test_invalid_none(self): + with self.assertRaises(ValidationError): + validate_no_empty(None) + + +class WhiteSpaceValidatorTestCase(SimpleTestCase): + def test_characters_only(self): + self.assertIsNone(validate_no_whitespace("test123")) + + def test_trailing_whitespace(self): + with self.assertRaises(ValidationError): + validate_no_whitespace("test123 ") + + def test_leading_whitespace(self): + with self.assertRaises(ValidationError): + validate_no_whitespace(" test123") + + def test_whitespace_in_between(self): + with self.assertRaises(ValidationError): + validate_no_whitespace("test 123") + + def test_whitespace_only(self): + with self.assertRaises(ValidationError): + validate_no_whitespace(" ") + + def test_trailing_tab_character(self): + with self.assertRaises(ValidationError): + validate_no_whitespace("test123\t") + + def test_leading_tab_character(self): + with self.assertRaises(ValidationError): + validate_no_whitespace("\ttest123") + + def test_tab_character_in_between(self): + with self.assertRaises(ValidationError): + validate_no_whitespace("test\t123") + + def test_tab_characters_only(self): + with self.assertRaises(ValidationError): + validate_no_whitespace("\t\t") diff --git a/src/objects/token/validators.py b/src/objects/token/validators.py new file mode 100644 index 00000000..21d23277 --- /dev/null +++ b/src/objects/token/validators.py @@ -0,0 +1,20 @@ +import re + +from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ + +# includes tabs, carriage returns, newlines, form-feeds and vertical whitespace characters +WHITESPACE_PATTERN = re.compile(r".*\s.*") + + +def validate_no_whitespace(value: str) -> None: + if WHITESPACE_PATTERN.match(value): + raise ValidationError( + code="all-whitespace", + message=_("Tokens cannot contain whitespace-like characters"), + ) + + +def validate_no_empty(value: str) -> None: + if not value: + raise ValidationError(code="invalid", message=_("Blank values are not allowed"))