From d6a9ca611d9a1e415b670b6d7aab5f1028af0150 Mon Sep 17 00:00:00 2001 From: Alexandre Saunier Date: Fri, 30 Sep 2022 10:54:18 +0200 Subject: [PATCH 1/2] Sanitize username where registering --- c2corg_api/tests/views/test_user.py | 29 +++++++++++++++++++++++++++++ c2corg_api/views/user.py | 23 ++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/c2corg_api/tests/views/test_user.py b/c2corg_api/tests/views/test_user.py index 8f8811808..77d4861d4 100644 --- a/c2corg_api/tests/views/test_user.py +++ b/c2corg_api/tests/views/test_user.py @@ -196,6 +196,35 @@ def test_register_forum_username_unique(self, _send_email): self.assertEqual(json['errors'][0]['description'], 'already used forum_username') + @patch('c2corg_api.emails.email_service.EmailService._send_email') + def test_register_stripped_username(self, _send_email): + request_body = { + 'username': ' contributor ', + 'forum_username': 'Foo', + 'name': 'Max Mustermann', + 'password': 'super secret', + 'email': 'some_user@camptocamp.org' + } + url = self._prefix + '/register' + json = self.app_post_json(url, request_body, status=400).json + self.assertEqual(json['errors'][0]['description'], + 'This username already exists') + + request_body = { + 'username': ' username with spaces ', + 'forum_username': 'Spaceman', + 'name': 'Max Mustermann', + 'password': 'super secret', + 'email': 'space@camptocamp.org' + } + url = self._prefix + '/register' + body = self.app_post_json(url, request_body, status=200).json + self.assertBodyEqual(body, 'username', 'username with spaces') + user_id = body.get('id') + user = self.session.query(User).get(user_id) + self.assertIsNotNone(user) + self.assertEqual(user.username, 'username with spaces') + @patch('c2corg_api.emails.email_service.EmailService._send_email') def test_register_username_email_not_equals_email(self, _send_email): request_body = { diff --git a/c2corg_api/views/user.py b/c2corg_api/views/user.py index 3f569fe66..2ec7593db 100644 --- a/c2corg_api/views/user.py +++ b/c2corg_api/views/user.py @@ -68,6 +68,27 @@ def validate_json_password(request, **kwargs): request.errors.add('body', 'password', 'Invalid') +def validate_json_username(request, **kwargs): + """Checks if the username was given, removes leading and trailing + whitespaces and eventually checks it's unique. + """ + + if 'username' not in request.json: + request.errors.add('body', 'username', 'Required') + return + + username = request.json['username'].strip() + if not username: + request.errors.add('body', 'username', + 'Username cannot be empty or whitespaces') + return + + if not is_unused_user_attribute('username', username, lowercase=True): + request.errors.add('body', 'username', 'This username already exists') + + request.validated['username'] = username + + def is_unused_user_attribute(attrname, value, lowercase=False): attr = getattr(User, attrname) query = DBSession.query(User) @@ -188,8 +209,8 @@ def __init__(self, request): validators=[ colander_body_validator, validate_json_password, + validate_json_username, partial(validate_unique_attribute, "email"), - partial(validate_unique_attribute, "username"), partial(validate_unique_attribute, "forum_username", lowercase=True), From 99d03a829be87a53520dabb620ff335818217553 Mon Sep 17 00:00:00 2001 From: Alexandre Saunier Date: Mon, 31 Oct 2022 10:47:40 +0100 Subject: [PATCH 2/2] Combine username validation with the one introduced for emails used as username --- c2corg_api/views/user.py | 51 +++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/c2corg_api/views/user.py b/c2corg_api/views/user.py index 2ec7593db..9f5c9c303 100644 --- a/c2corg_api/views/user.py +++ b/c2corg_api/views/user.py @@ -68,27 +68,6 @@ def validate_json_password(request, **kwargs): request.errors.add('body', 'password', 'Invalid') -def validate_json_username(request, **kwargs): - """Checks if the username was given, removes leading and trailing - whitespaces and eventually checks it's unique. - """ - - if 'username' not in request.json: - request.errors.add('body', 'username', 'Required') - return - - username = request.json['username'].strip() - if not username: - request.errors.add('body', 'username', - 'Username cannot be empty or whitespaces') - return - - if not is_unused_user_attribute('username', username, lowercase=True): - request.errors.add('body', 'username', 'This username already exists') - - request.validated['username'] = username - - def is_unused_user_attribute(attrname, value, lowercase=False): attr = getattr(User, attrname) query = DBSession.query(User) @@ -144,21 +123,36 @@ def validate_forum_username(request, **kwargs): def validate_username(request, **kwargs): + """Checks username is set, strips leading/trailing whitespaces, + checks unicity and if an email, that it matches the provided email. """ - Check that the username is not an email, - or that it is the same as the actual email. - """ - if 'username' in request.json and 'email' in request.json: - value = request.json['username'] + + if 'username' not in request.json: + request.errors.add('body', 'username', 'Required') + return + + username = request.json['username'].strip() + if not username: + request.errors.add('body', 'username', + 'Username cannot be empty or whitespaces') + return + + if not is_unused_user_attribute('username', username, lowercase=True): + request.errors.add('body', 'username', 'This username already exists') + + # Check that the username is not an email, + # or that it is the same as the actual email. + if 'email' in request.json: email = request.json['email'] - if (is_valid_email(value) and email != value): + if (is_valid_email(username) and email != username): request.errors.add( 'body', 'username', 'An email address used as username should be the same as the' + ' one used as the account email address.') return - request.validated['username'] = value + + request.validated['username'] = username def validate_captcha(request, **kwargs): @@ -209,7 +203,6 @@ def __init__(self, request): validators=[ colander_body_validator, validate_json_password, - validate_json_username, partial(validate_unique_attribute, "email"), partial(validate_unique_attribute, "forum_username",