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 import pytest
from flask import Flask from flask import Flask
from fittrackee.users.exceptions import UserNotFoundException from fittrackee.users.exceptions import UserNotFoundException
from fittrackee.users.models import User 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 from ..utils import random_string
@ -28,3 +36,179 @@ class TestSetAdminRights:
set_admin_rights(user_1_admin.username) set_admin_rights(user_1_admin.username)
assert user_1_admin.admin is True 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 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( def register_controls(
username: str, email: str, password: str, password_conf: str username: str, email: str, password: str, password_conf: str
) -> str: ) -> str:
@ -45,9 +60,7 @@ def register_controls(
If not, it returns not empty string If not, it returns not empty string
""" """
ret = '' ret = check_username(username)
if not 2 < len(username) < 13:
ret += 'username: 3 to 12 characters required\n'
if not is_valid_email(email): if not is_valid_email(email):
ret += 'email: valid email must be provided\n' ret += 'email: valid email must be provided\n'
ret += check_passwords(password, password_conf) ret += check_passwords(password, password_conf)

View File

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

View File

@ -17,13 +17,11 @@
"no selected file": "No selected file.", "no selected file": "No selected file.",
"password: password and password confirmation do not match": "Password: password and password confirmation don't match.", "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.", "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.", "sorry, that user already exists": "Sorry, that user already exists.",
"sport does not exist": "Sport does not exist.", "sport does not exist": "Sport does not exist.",
"signature expired, please log in again": "Signature expired. Please log in again.", "signature expired, please log in again": "Signature expired. Please log in again.",
"successfully registered": "Successfully registered.", "successfully registered": "Successfully registered.",
"user does not exist": "User does not exist.", "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 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." "you do not have permissions": "You do not have permissions."
}, },

View File

@ -17,13 +17,11 @@
"Network Error": "Erreur Réseau.", "Network Error": "Erreur Réseau.",
"password: password and password confirmation do not match": "Mot de passe : les mots de passe saisis sont différents.", "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.", "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.", "sport does not exist": "Ce sport n'existe pas.",
"signature expired, please log in again": "Signature expirée. Merci de vous reconnecter.", "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à.", "sorry, that user already exists": "Désolé, cet utilisateur existe déjà.",
"successfully registered": "Inscription validée.", "successfully registered": "Inscription validée.",
"user does not exist": "L'utilisateur n'existe pas", "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 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." "you do not have permissions": "Vous n'avez pas les permissions nécessaires."
}, },