feat(usermodel): implement User parameter validation methods

This commit is contained in:
Julian Lobbes 2022-11-11 00:46:58 +01:00
parent 16fb570cfd
commit 876dc2ef5c
4 changed files with 61 additions and 23 deletions

View File

@ -11,6 +11,7 @@ services:
- ./lumi2/__init__.py:/app/lumi2/__init__.py:ro
- ./lumi2/exceptions.py:/app/lumi2/exceptions.py:ro
- ./lumi2/ldap.py:/app/lumi2/ldap.py:ro
- ./lumi2/usermodel.py:/app/lumi2/usermodel.py:ro
- ./lumi2/usermanager.py:/app/lumi2/usermanager.py:ro
- ./lumi2/static/:/app/lumi2/static/:ro
- ./lumi2/templates/:/app/lumi2/templates/:ro

View File

@ -596,7 +596,7 @@ def get_user(connection: Connection, uid: str) -> User:
'sn',
'displayName',
'uid',
'password',
'userPassword',
'jpegPhoto',
'mail',
]

View File

@ -4,6 +4,10 @@ Also provides methods to convert LDAP user/group entries into user/group objects
and vice versa.
"""
from string import ascii_lowercase, ascii_uppercase, digits, whitespace
from base64 import b64decode
from binascii import Error as Base64DecodeError
from PIL import Image
class User:
@ -12,23 +16,17 @@ class User:
Attributes
----------
username : str
The user's username. Can contain only uppercase/lowercase latin characters,
numbers, hyphens, underscores and periods. Must start with a latin character.
Minimum length is 1, maximum length is 64 characters.
The user's username.
password_hash : str
Base64-encoded SHA512 hash of the user's password.
email : str
The user's email address.
Must contain an '@'-character, may not contain any whitespace.
first_name : str
The user's first name.
May not contain any whitespace.
last_name : str
The user's last name.
May not contain any whitespace.
display_name : str
The user's display name (as required by some LDAP-enabled applications).
May not contain any whitespace.
picture : PIL.Image
The user's profile picture as a PIL Image object.
"""
@ -62,17 +60,28 @@ class User:
if not isinstance(input_str, str):
raise TypeError(f"Expected a string but got: '{type(input_str)}'.")
# TODO implement
return False
if not len(input_str):
return False
if len(input_str) > 64:
return False
if input_str[0] not in ascii_lowercase + ascii_uppercase:
return False
valid_chars = ascii_lowercase + ascii_uppercase + digits + "-_."
for char in input_str:
if char not in valid_chars:
return False
return True
@staticmethod
def is_valid_password_hash(input_str: str) -> bool:
"""Checks whether the input string is a valid password hash.
Password hashes are base64-encoded bytes, and the hashing algorithm used
to create the hash is hard to determine, but we can verify that the
provided hash is a non-empty string containing base64-decodeable text.
A valid password hash is a non-empty string containing base64-decodeable
text.
Parameters
----------
@ -93,15 +102,24 @@ class User:
if not isinstance(input_str, str):
raise TypeError(f"Expected a string but got: '{type(input_str)}'.")
# TODO implement
return False
if not len(input_str):
return False
try:
b64decode(input_str, validate=True)
return True
except Base64DecodeError:
return False
@staticmethod
def is_valid_email(input_str: str) -> bool:
"""Checks whether the input string is a valid email address.
A valid email address contains no whitespace and at least one '@'-char.
WARNING: this validation is very rudimentary. Proper validation requires
a validation email to be sent and confirmed by the user.
A valid email address contains no whitespace, at least one '@' character,
and a '.' character somewhere after the '@'.
Parameters
----------
@ -122,8 +140,18 @@ class User:
if not isinstance(input_str, str):
raise TypeError(f"Expected a string but got: '{type(input_str)}'.")
# TODO implement
return False
if '@' not in input_str:
return False
if '.' not in input_str:
return False
if '.' not in input_str.split('@')[1]:
return False
for char in input_str:
if char in whitespace:
return False
return True
@staticmethod
@ -152,8 +180,14 @@ class User:
if not isinstance(input_str, str):
raise TypeError(f"Expected a string but got: '{type(input_str)}'.")
# TODO implement
return False
if not len(input_str):
return False
for char in input_str:
if char in whitespace:
return False
return True
@staticmethod
@ -181,6 +215,10 @@ class User:
if not isinstance(input_image, Image):
raise TypeError(f"Expected a PIL Image but got: '{type(input_image)}'.")
# TODO implement
return True
def __init__(
self,
username: str, password_hash: str, email: str,

View File

@ -41,8 +41,7 @@ def test_is_valid_password_hash():
valid_hashes = [
# can contain [A-Za-z0-9+/] and up to two '=' at the end
"foobar", "EzM3",
"abcABC123+/=", "a",
"EzM3", "abcABC123+/=",
]
for valid_hash in valid_hashes:
assert User.is_valid_password_hash(valid_hash)
@ -55,7 +54,7 @@ def test_is_valid_email():
invalid_emails = [
"", " ", "\t", "\n",
" alice@example.com", "alice", "@", "@.",
" alice@example.com", "alice", "@", "alice@com"
"alice@example.com ", "alice@ex ample.com"
]
for invalid_email in invalid_emails: