Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow everything but unicode emojis #1178

Merged
merged 5 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dds_web/api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
13 changes: 3 additions & 10 deletions dds_web/api/schemas/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."},
Expand Down Expand Up @@ -123,15 +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."""
return "{pid}-{tstamp}-{rstring}".format(
Expand Down
24 changes: 24 additions & 0 deletions dds_web/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
59 changes: 59 additions & 0 deletions tests/test_project_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import unittest
import time
import os
from urllib import response

# Installed
import pytest
Expand Down Expand Up @@ -690,3 +691,61 @@ 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