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

Fixed User registration request data validation #95

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/api/dao/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from app.api.email_utils import confirm_token
from app.database.models.user import UserModel
from app.utils.email_utils import is_email_valid
from app.utils.validation_utils import is_email_valid


class UserDAO:
Expand Down
46 changes: 3 additions & 43 deletions app/api/resources/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from flask_jwt_extended import jwt_required, create_access_token, get_jwt_identity
from flask_restplus import Resource, marshal, Namespace

from app.utils.email_utils import is_email_valid
from app.api.validations.user import validate_user_registration_request_data, validate_resend_email_request_data
from app.api.email_utils import send_email_verification_message
from app.api.models.user import *
from app.api.dao.user import UserDAO
Expand Down Expand Up @@ -152,7 +152,7 @@ def post(cls):

data = request.json

is_valid = cls.is_valid_data(data)
is_valid = validate_user_registration_request_data(data)

if is_valid != {}:
return is_valid, 400
Expand All @@ -164,33 +164,6 @@ def post(cls):

return result

@staticmethod
def is_valid_data(data):

# Verify if request body has required fields
if 'name' not in data:
return {"message": "Name field is missing."}
if 'username' not in data:
return {"message": "Username field is missing."}
if 'password' not in data:
return {"message": "Password field is missing."}
if 'email' not in data:
return {"message": "Email field is missing."}
if 'terms_and_conditions_checked' not in data:
return {"message": "Terms and conditions field is missing."}

terms_and_conditions_checked = data['terms_and_conditions_checked']
email = data['email']

# Verify business logic of request body
if terms_and_conditions_checked is False:
return {"message": "Terms and conditions are not checked."}

if not is_email_valid(email):
return {"message": "Your email is invalid."}

return {}


@users_ns.route('user/confirm_email/<string:token>')
@users_ns.param('token', 'Token sent to the user\'s email')
Expand All @@ -210,7 +183,7 @@ def post(cls):

data = request.json

is_valid = cls.is_valid_data(data)
is_valid = validate_resend_email_request_data(data)

if is_valid != {}:
return is_valid, 400
Expand All @@ -226,19 +199,6 @@ def post(cls):

return {"message": "Check your email, a new verification email was sent."}, 200

@staticmethod
def is_valid_data(data):

# Verify if request body has required fields
if 'email' not in data:
return {"message": "Email field is missing."}

email = data['email']
if not is_email_valid(email):
return {"message": "Your email is invalid."}

return {}


@users_ns.route('login')
class LoginUser(Resource):
Expand Down
Empty file added app/api/validations/__init__.py
Empty file.
73 changes: 73 additions & 0 deletions app/api/validations/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from app.utils.validation_utils import is_email_valid, is_username_valid

# Field character limit

NAME_MAX_LENGTH = 30
NAME_MIN_LENGTH = 2
USERNAME_MAX_LENGTH = 25
USERNAME_MIN_LENGTH = 5
PASSWORD_MAX_LENGTH = 25
PASSWORD_MIN_LENGTH = 8


def validate_user_registration_request_data(data):
# Verify if request body has required fields
if 'name' not in data:
return {"message": "Name field is missing."}
if 'username' not in data:
return {"message": "Username field is missing."}
if 'password' not in data:
return {"message": "Password field is missing."}
if 'email' not in data:
return {"message": "Email field is missing."}
if 'terms_and_conditions_checked' not in data:
return {"message": "Terms and conditions field is missing."}

name = data['name']
username = data['username']
password = data['password']
email = data['email']
terms_and_conditions_checked = data['terms_and_conditions_checked']

if not (isinstance(name, str) and isinstance(username, str) and isinstance(password, str)):
return {"message": "Name, username and password must be in string format."}

if not (NAME_MIN_LENGTH <= len(name) <= NAME_MAX_LENGTH):
return {"message": "The name field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=NAME_MIN_LENGTH-1,
max_limit=NAME_MAX_LENGTH+1)}

if not (USERNAME_MIN_LENGTH <= len(username) <= USERNAME_MAX_LENGTH):
return {"message": "The username field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=USERNAME_MIN_LENGTH-1,
max_limit=USERNAME_MAX_LENGTH+1)}

if not (PASSWORD_MIN_LENGTH <= len(password) <= PASSWORD_MAX_LENGTH):
return {"message": "The password field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=PASSWORD_MIN_LENGTH-1,
max_limit=PASSWORD_MAX_LENGTH+1)}

# Verify business logic of request body
if terms_and_conditions_checked is False:
return {"message": "Terms and conditions are not checked."}

if not is_email_valid(email):
return {"message": "Your email is invalid."}

if not is_username_valid(username):
return {"message": "Your username is invalid."}

return {}


def validate_resend_email_request_data(data):

# Verify if request body has required fields
if 'email' not in data:
return {"message": "Email field is missing."}

email = data['email']
if not is_email_valid(email):
return {"message": "Your email is invalid."}

return {}
5 changes: 5 additions & 0 deletions app/utils/email_utils.py → app/utils/validation_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import re

email_regex = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
username_regex = r"(^[a-zA-Z0-9]+$)"


def is_email_valid(email):
return re.match(email_regex, email)


def is_username_valid(username):
return re.match(username_regex, username)
189 changes: 189 additions & 0 deletions tests/users/test_validation_request_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import unittest
from random import SystemRandom
from string import ascii_lowercase

from app.api.validations.user import validate_user_registration_request_data, NAME_MIN_LENGTH, NAME_MAX_LENGTH, \
USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH
from tests.test_data import user1


class TestUserApiRequestDataValidation(unittest.TestCase):

def test_user_registration_valid_request_data(self):
expected_result = {}
request_body = dict(
name=user1['name'],
username=user1['username'],
password=user1['password'],
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_missing_name_field(self):
expected_result = {"message": "Name field is missing."}
request_body = dict(
username=user1['username'],
password=user1['password'],
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_missing_username_field(self):
expected_result = {"message": "Username field is missing."}
request_body = dict(
name=user1['name'],
password=user1['password'],
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_missing_password_field(self):
expected_result = {"message": "Password field is missing."}
request_body = dict(
name=user1['name'],
username=user1['username'],
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_missing_email_field(self):
expected_result = {"message": "Email field is missing."}
request_body = dict(
name=user1['name'],
username=user1['username'],
password=user1['password'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_missing_terms_and_conditions_field(self):
expected_result = {"message": "Terms and conditions field is missing."}
request_body = dict(
name=user1['name'],
username=user1['username'],
password=user1['password'],
email=user1['email'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_with_terms_unchecked(self):
expected_result = {"message": "Terms and conditions are not checked."}
request_body = dict(
name=user1['name'],
username=user1['username'],
password=user1['password'],
email=user1['email'],
terms_and_conditions_checked=False)
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_name_inferior_to_limit(self):
secure_random = SystemRandom()
expected_result = {"message": "The name field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=NAME_MIN_LENGTH-1,
max_limit=NAME_MAX_LENGTH+1)}
random_generated_name = "".join(secure_random.choice(ascii_lowercase) for x in range(NAME_MIN_LENGTH-1))
request_body = dict(
name=random_generated_name,
username=user1['username'],
password=user1['password'],
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_name_superior_to_limit(self):
secure_random = SystemRandom()
expected_result = {"message": "The name field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=NAME_MIN_LENGTH-1,
max_limit=NAME_MAX_LENGTH+1)}
random_generated_name = "".join(secure_random.choice(ascii_lowercase) for x in range(NAME_MAX_LENGTH+1))
request_body = dict(
name=random_generated_name,
username=user1['username'],
password=user1['password'],
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_username_inferior_to_limit(self):
secure_random = SystemRandom()
expected_result = {"message": "The username field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=USERNAME_MIN_LENGTH-1,
max_limit=USERNAME_MAX_LENGTH+1)}
random_generated_username = "".join(secure_random.choice(ascii_lowercase) for x in range(USERNAME_MIN_LENGTH-1))
request_body = dict(
name=user1['name'],
username=random_generated_username,
password=user1['password'],
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_username_superior_to_limit(self):
secure_random = SystemRandom()
expected_result = {"message": "The username field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=USERNAME_MIN_LENGTH-1,
max_limit=USERNAME_MAX_LENGTH+1)}
random_generated_username = "".join(secure_random.choice(ascii_lowercase) for x in range(USERNAME_MAX_LENGTH+1))
request_body = dict(
name=user1['name'],
username=random_generated_username,
password=user1['password'],
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_password_inferior_to_limit(self):
secure_random = SystemRandom()
expected_result = {"message": "The password field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=PASSWORD_MIN_LENGTH-1,
max_limit=PASSWORD_MAX_LENGTH+1)}
random_generated_password = "".join(secure_random.choice(ascii_lowercase) for x in range(PASSWORD_MIN_LENGTH-1))
request_body = dict(
name=user1['name'],
username=user1['username'],
password=random_generated_password,
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)

def test_user_registration_request_data_password_superior_to_limit(self):
secure_random = SystemRandom()
expected_result = {"message": "The password field has to longer than {min_limit} "
"characters and shorter than {max_limit} characters.".format(min_limit=PASSWORD_MIN_LENGTH-1,
max_limit=PASSWORD_MAX_LENGTH+1)}
random_generated_password = "".join(secure_random.choice(ascii_lowercase) for x in range(PASSWORD_MAX_LENGTH+1))
request_body = dict(
name=user1['name'],
username=user1['username'],
password=random_generated_password,
email=user1['email'],
terms_and_conditions_checked=user1['terms_and_conditions_checked'])
actual_result = validate_user_registration_request_data(request_body)

self.assertEqual(expected_result, actual_result)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import unittest
from app.utils.email_utils import is_email_valid
from app.utils.validation_utils import is_email_valid


class TestEmailUtils(unittest.TestCase):
class TestEmailValidation(unittest.TestCase):

def test_empty_email(self):
email = ""
Expand Down