From 9410eab57473d1c52c5032eabe793fcb17c80cd3 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 18 May 2022 16:45:40 +0200 Subject: [PATCH 1/4] allow characters in description again --- dds_web/api/project.py | 2 +- dds_web/api/schemas/project_schemas.py | 12 +++--------- dds_web/utils.py | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 7a1aac301..543ebc2cf 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -611,7 +611,7 @@ def post(self): try: new_project = project_schemas.CreateProjectSchema().load(p_info) db.session.add(new_project) - except sqlalchemy.exc.OperationalError as err: + except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err: raise DatabaseError(message=str(err), alt_message="Unexpected database error.") if not new_project: diff --git a/dds_web/api/schemas/project_schemas.py b/dds_web/api/schemas/project_schemas.py index 50f2e64c1..09565863e 100644 --- a/dds_web/api/schemas/project_schemas.py +++ b/dds_web/api/schemas/project_schemas.py @@ -79,7 +79,9 @@ class Meta: description = marshmallow.fields.String( required=True, allow_none=False, - validate=marshmallow.validate.Length(min=1), + validate=marshmallow.validate.And( + marshmallow.validate.Length(min=1), dds_web.utils.contains_unicode_emojis + ), error_messages={ "required": {"message": "A project description is required."}, "null": {"message": "A project description is required."}, @@ -123,14 +125,6 @@ def validate_all_fields(self, data, **kwargs): ): raise marshmallow.ValidationError("Missing fields!") - @marshmallow.validates("description") - def validate_description(self, value): - """Verify that description only has words, spaces and . / ,.""" - disallowed = re.findall(r"[^(\w\s.,)]+", value) - if disallowed: - raise marshmallow.ValidationError( - message="The description can only contain letters, spaces, period and commas." - ) def generate_bucketname(self, public_id, created_time): """Create bucket name for the given project.""" diff --git a/dds_web/utils.py b/dds_web/utils.py index d730acd28..06968fee9 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -67,6 +67,30 @@ def contains_disallowed_characters(indata): ) +def contains_unicode_emojis(indata): + """Find unicode emojis in string - cause SQLAlchemyErrors.""" + # Ref: https://gist.github.com/Alex-Just/e86110836f3f93fe7932290526529cd1#gistcomment-3208085 + # Ref: https://en.wikipedia.org/wiki/Unicode_block + EMOJI_PATTERN = re.compile( + "([" + "\U0001F1E0-\U0001F1FF" # flags (iOS) + "\U0001F300-\U0001F5FF" # symbols & pictographs + "\U0001F600-\U0001F64F" # emoticons + "\U0001F680-\U0001F6FF" # transport & map symbols + "\U0001F700-\U0001F77F" # alchemical symbols + "\U0001F780-\U0001F7FF" # Geometric Shapes Extended + "\U0001F800-\U0001F8FF" # Supplemental Arrows-C + "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs + "\U0001FA00-\U0001FA6F" # Chess Symbols + "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A + "\U00002702-\U000027B0" # Dingbats + "])" + ) + emojis = re.findall(EMOJI_PATTERN, indata) + if emojis: + raise marshmallow.ValidationError(f"This input is not allowed: {''.join(emojis)}") + + def email_not_taken(indata): """Validator - verify that email is not taken. From c7dc3b795a1df8809a3990ea5321fef34d60e748 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 18 May 2022 16:52:40 +0200 Subject: [PATCH 2/4] black --- dds_web/api/schemas/project_schemas.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/api/schemas/project_schemas.py b/dds_web/api/schemas/project_schemas.py index 09565863e..a5cfcfa08 100644 --- a/dds_web/api/schemas/project_schemas.py +++ b/dds_web/api/schemas/project_schemas.py @@ -125,7 +125,6 @@ def validate_all_fields(self, data, **kwargs): ): raise marshmallow.ValidationError("Missing fields!") - def generate_bucketname(self, public_id, created_time): """Create bucket name for the given project.""" return "{pid}-{tstamp}-{rstring}".format( From 1afe7b8157484f95f22e1ca12312f0c817024597 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 18 May 2022 17:16:06 +0200 Subject: [PATCH 3/4] added tests --- tests/test_project_creation.py | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test_project_creation.py b/tests/test_project_creation.py index fdaa5559a..fd5a7def8 100644 --- a/tests/test_project_creation.py +++ b/tests/test_project_creation.py @@ -7,6 +7,7 @@ import unittest import time import os +from urllib import response # Installed import pytest @@ -48,6 +49,7 @@ } + def create_unit_admins(num_admins, unit_id=1): new_admins = [] for i in range(1, num_admins + 1): @@ -690,3 +692,47 @@ def test_create_project_with_unsuitable_roles(client, boto3_session): assert response.json and response.json.get("user_addition_statuses") for x in response.json.get("user_addition_statuses"): assert "User Role should be either 'Project Owner' or 'Researcher'" in x + +def test_create_project_valid_characters(client, boto3_session): + """Create a project with no unicode.""" + # Project info with valid characters + proj_data_val_chars = proj_data.copy() + proj_data_val_chars["description"] = "A longer project description !#¤%&/()=?¡@£$€¥{[]}\\" + + create_unit_admins(num_admins=2) + + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins == 3 + + response = client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + json=proj_data_val_chars, + ) + assert response.status_code == http.HTTPStatus.OK + + new_project = db.session.query(models.Project).filter(models.Project.description == proj_data_val_chars["description"]).first() + assert new_project + +def test_create_project_invalid_characters(client, boto3_session): + """Create a project with unicode characters.""" + # Project info with invalid characters + proj_data_inval_chars = proj_data.copy() + proj_data_inval_chars["description"] = "A longer project description \U0001F300 \U0001F601" + + create_unit_admins(num_admins=2) + + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins == 3 + + response = client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + json=proj_data_inval_chars, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert response.json and response.json.get("description") and isinstance(response.json.get("description"), list) + assert response.json["description"][0] == "This input is not allowed: \U0001F300\U0001F601" + + new_project = db.session.query(models.Project).filter(models.Project.description == proj_data_inval_chars["description"]).first() + assert not new_project \ No newline at end of file From 108fbdc3da40669ad68f1c1a94b5c9f6e6271b15 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 18 May 2022 17:16:46 +0200 Subject: [PATCH 4/4] black --- tests/test_project_creation.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/tests/test_project_creation.py b/tests/test_project_creation.py index fd5a7def8..f48dc3684 100644 --- a/tests/test_project_creation.py +++ b/tests/test_project_creation.py @@ -49,7 +49,6 @@ } - def create_unit_admins(num_admins, unit_id=1): new_admins = [] for i in range(1, num_admins + 1): @@ -693,6 +692,7 @@ def test_create_project_with_unsuitable_roles(client, boto3_session): for x in response.json.get("user_addition_statuses"): assert "User Role should be either 'Project Owner' or 'Researcher'" in x + def test_create_project_valid_characters(client, boto3_session): """Create a project with no unicode.""" # Project info with valid characters @@ -705,15 +705,20 @@ def test_create_project_valid_characters(client, boto3_session): assert current_unit_admins == 3 response = client.post( - tests.DDSEndpoint.PROJECT_CREATE, + tests.DDSEndpoint.PROJECT_CREATE, headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), json=proj_data_val_chars, ) assert response.status_code == http.HTTPStatus.OK - - new_project = db.session.query(models.Project).filter(models.Project.description == proj_data_val_chars["description"]).first() + + new_project = ( + db.session.query(models.Project) + .filter(models.Project.description == proj_data_val_chars["description"]) + .first() + ) assert new_project + def test_create_project_invalid_characters(client, boto3_session): """Create a project with unicode characters.""" # Project info with invalid characters @@ -726,13 +731,21 @@ def test_create_project_invalid_characters(client, boto3_session): assert current_unit_admins == 3 response = client.post( - tests.DDSEndpoint.PROJECT_CREATE, + tests.DDSEndpoint.PROJECT_CREATE, headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), json=proj_data_inval_chars, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert response.json and response.json.get("description") and isinstance(response.json.get("description"), list) + assert ( + response.json + and response.json.get("description") + and isinstance(response.json.get("description"), list) + ) assert response.json["description"][0] == "This input is not allowed: \U0001F300\U0001F601" - new_project = db.session.query(models.Project).filter(models.Project.description == proj_data_inval_chars["description"]).first() - assert not new_project \ No newline at end of file + new_project = ( + db.session.query(models.Project) + .filter(models.Project.description == proj_data_inval_chars["description"]) + .first() + ) + assert not new_project