API & Client - allow only alphanumeric characters and '_' in username

This commit is contained in:
Sam 2022-02-13 12:08:24 +01:00
parent 66cd3c9655
commit 94a6f2fa9c
5 changed files with 206 additions and 8 deletions

View File

@ -1,9 +1,17 @@
from unittest.mock import patch
import pytest
from flask import Flask
from fittrackee.users.exceptions import UserNotFoundException
from fittrackee.users.models import User
from fittrackee.users.utils import set_admin_rights
from fittrackee.users.utils import (
check_passwords,
check_username,
is_valid_email,
register_controls,
set_admin_rights,
)
from ..utils import random_string
@ -28,3 +36,179 @@ class TestSetAdminRights:
set_admin_rights(user_1_admin.username)
assert user_1_admin.admin is True
class TestIsValidEmail:
@pytest.mark.parametrize(
('input_email',),
[
('',),
('foo',),
('foo@',),
('@foo.fr',),
('foo@foo',),
('.',),
('./',),
],
)
def test_it_returns_false_if_email_is_invalid(
self, input_email: str
) -> None:
assert is_valid_email(input_email) is False
@pytest.mark.parametrize(
('input_email',),
[
('admin@example.com',),
('admin@test.example.com',),
('admin.site@test.example.com',),
('admin-site@test-example.com',),
],
)
def test_it_returns_true_if_email_is_valid(self, input_email: str) -> None:
assert is_valid_email(input_email) is True
class TestCheckPasswords:
def test_it_returns_error_message_string_if_passwords_do_not_match(
self,
) -> None:
assert check_passwords('password', 'pasword') == (
'password: password and password confirmation do not match\n'
)
@pytest.mark.parametrize(
('input_password_length',),
[
(0,),
(3,),
(7,),
],
)
def test_it_returns_error_message_string_if_password_length_is_below_8_characters( # noqa
self, input_password_length: int
) -> None:
password = random_string(input_password_length)
assert check_passwords(password, password) == (
'password: 8 characters required\n'
)
@pytest.mark.parametrize(
('input_password_length',),
[
(8,),
(10,),
],
)
def test_it_returns_empty_string_when_password_length_exceeds_7_characters(
self, input_password_length: int
) -> None:
password = random_string(input_password_length)
assert check_passwords(password, password) == ''
def test_it_returns_multiple_errors(self) -> None:
password = random_string(3)
password_conf = random_string(8)
assert check_passwords(password, password_conf) == (
'password: password and password confirmation do not match\n'
'password: 8 characters required\n'
)
class TestIsUsernameValid:
@pytest.mark.parametrize(
('input_username_length',),
[
(2,),
(13,),
],
)
def test_it_returns_error_message_when_username_length_is_invalid(
self, input_username_length: int
) -> None:
assert (
check_username(
username=random_string(input_username_length),
)
== 'username: 3 to 12 characters required\n'
)
@pytest.mark.parametrize(
('input_invalid_character',),
[
('.',),
('/',),
('$',),
],
)
def test_it_returns_error_message_when_username_has_invalid_character(
self, input_invalid_character: str
) -> None:
username = random_string() + input_invalid_character
assert check_username(username=username) == (
'username: only alphanumeric characters and the '
'underscore character "_" allowed\n'
)
def test_it_returns_empty_string_when_username_is_valid(self) -> None:
assert check_username(username=random_string()) == ''
def test_it_returns_multiple_errors(self) -> None:
username = random_string(1) + '.'
assert check_username(username=username) == (
'username: 3 to 12 characters required\n'
'username: only alphanumeric characters and the underscore '
'character "_" allowed\n'
)
class TestRegisterControls:
module_path = 'fittrackee.users.utils.'
valid_username = random_string()
valid_email = f'{random_string()}@example.com'
valid_password = random_string()
def test_it_calls_all_validators(self) -> None:
with patch(
self.module_path + 'check_passwords'
) as check_passwords_mock, patch(
self.module_path + 'check_username'
) as check_username_mock, patch(
self.module_path + 'is_valid_email'
) as is_valid_email_mock:
register_controls(
self.valid_username,
self.valid_email,
self.valid_password,
self.valid_password,
)
check_passwords_mock.assert_called_once_with(
self.valid_password, self.valid_password
)
check_username_mock.assert_called_once_with(self.valid_username)
is_valid_email_mock.assert_called_once_with(self.valid_email)
def test_it_returns_empty_string_when_inputs_are_valid(self) -> None:
assert (
register_controls(
self.valid_username,
self.valid_email,
self.valid_password,
self.valid_password,
)
== ''
)
def test_it_returns_multiple_errors_when_inputs_are_invalid(self) -> None:
invalid_username = random_string(2)
assert register_controls(
username=invalid_username,
email=invalid_username,
password=random_string(8),
password_conf=random_string(8),
) == (
'username: 3 to 12 characters required\n'
'email: valid email must be provided\n'
'password: password and password confirmation do not match\n'
)

View File

@ -37,6 +37,21 @@ def check_passwords(password: str, password_conf: str) -> str:
return ret
def check_username(username: str) -> str:
"""
Return if username is valid
"""
ret = ''
if not 2 < len(username) < 13:
ret += 'username: 3 to 12 characters required\n'
if not re.match(r'^[a-zA-Z0-9_]+$', username):
ret += (
'username: only alphanumeric characters and the '
'underscore character "_" allowed\n'
)
return ret
def register_controls(
username: str, email: str, password: str, password_conf: str
) -> str:
@ -45,9 +60,7 @@ def register_controls(
If not, it returns not empty string
"""
ret = ''
if not 2 < len(username) < 13:
ret += 'username: 3 to 12 characters required\n'
ret = check_username(username)
if not is_valid_email(email):
ret += 'email: valid email must be provided\n'
ret += check_passwords(password, password_conf)

View File

@ -21,6 +21,9 @@
id="username"
:disabled="registration_disabled"
required
pattern="[a-zA-Z0-9_]+"
minlength="3"
maxlength="12"
@invalid="invalidateForm"
v-model="formData.username"
:placeholder="$t('user.USERNAME')"
@ -46,6 +49,7 @@
required
@invalid="invalidateForm"
type="password"
minlength="8"
v-model="formData.password"
:placeholder="
action === 'reset'
@ -58,6 +62,7 @@
id="confirm-password"
:disabled="registration_disabled"
type="password"
minlength="8"
required
@invalid="invalidateForm"
v-model="formData.password_conf"

View File

@ -17,13 +17,11 @@
"no selected file": "No selected file.",
"password: password and password confirmation do not match": "Password: password and password confirmation don't match.",
"provide a valid auth token": "Provide a valid auth token.",
"password: 8 characters required": "Password: 8 characters required.",
"sorry, that user already exists": "Sorry, that user already exists.",
"sport does not exist": "Sport does not exist.",
"signature expired, please log in again": "Signature expired. Please log in again.",
"successfully registered": "Successfully registered.",
"user does not exist": "User does not exist.",
"username: 3 to 12 characters required": "Username: 3 to 12 characters required.",
"you can not delete your account, no other user has admin rights": "You can not delete your account, no other user has admin rights.",
"you do not have permissions": "You do not have permissions."
},

View File

@ -17,13 +17,11 @@
"Network Error": "Erreur Réseau.",
"password: password and password confirmation do not match": "Mot de passe : les mots de passe saisis sont différents.",
"provide a valid auth token": "Merci de fournir un jeton de connexion valide.",
"password: 8 characters required": "Mot de passe : 8 caractères minimum.",
"sport does not exist": "Ce sport n'existe pas.",
"signature expired, please log in again": "Signature expirée. Merci de vous reconnecter.",
"sorry, that user already exists": "Désolé, cet utilisateur existe déjà.",
"successfully registered": "Inscription validée.",
"user does not exist": "L'utilisateur n'existe pas",
"username: 3 to 12 characters required": "Nom d'utilisateur : 3 à 12 caractères requis.",
"you can not delete your account, no other user has admin rights": "Vous ne pouvez pas supprimer votre compte, aucun autre utilisateur n'a des droits d'administration.",
"you do not have permissions": "Vous n'avez pas les permissions nécessaires."
},