From 8c1f8775c418fdd7eeac671d60f21177162a96ed Mon Sep 17 00:00:00 2001 From: Julian Lobbes Date: Tue, 15 Nov 2022 15:10:38 +0100 Subject: [PATCH] feat(usermodel): add default profile picture --- lumi2/static/assets/default_user_icon.jpg | Bin 0 -> 10257 bytes lumi2/static/assets/default_user_icon.svg | 94 ++++++++++++++++++++++ lumi2/usermodel.py | 81 ++++++++++++++++--- 3 files changed, 163 insertions(+), 12 deletions(-) create mode 100644 lumi2/static/assets/default_user_icon.jpg create mode 100644 lumi2/static/assets/default_user_icon.svg diff --git a/lumi2/static/assets/default_user_icon.jpg b/lumi2/static/assets/default_user_icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5bc265e4c19a9070542a141cc36cc00784461e9 GIT binary patch literal 10257 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 G&iy|WSvO1o literal 0 HcmV?d00001 diff --git a/lumi2/static/assets/default_user_icon.svg b/lumi2/static/assets/default_user_icon.svg new file mode 100644 index 0000000..4bc4ef2 --- /dev/null +++ b/lumi2/static/assets/default_user_icon.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + ? + + diff --git a/lumi2/usermodel.py b/lumi2/usermodel.py index 3553b78..0a6ea98 100644 --- a/lumi2/usermodel.py +++ b/lumi2/usermodel.py @@ -1,10 +1,12 @@ """Provides the application-internal class-based models for users and groups.""" from string import ascii_lowercase, ascii_uppercase, digits, whitespace -from base64 import b64decode +from base64 import b64encode, b64decode +import hashlib from binascii import Error as Base64DecodeError -from PIL.Image import Image +from PIL import Image +from flask import current_app class User: """Class model for a user. @@ -187,7 +189,7 @@ class User: @staticmethod - def is_valid_picture(input_image: Image) -> bool: + def is_valid_picture(input_image: Image.Image) -> bool: """Checks whether the input image is a valid Image object. TBD - unsure which formats and filesizes to allow here. @@ -208,18 +210,64 @@ class User: If input_image is not of type PIL.Image. """ - if not isinstance(input_image, Image): + if not isinstance(input_image, Image.Image): raise TypeError(f"Expected a PIL Image but got: '{type(input_image)}'.") - # TODO implement + # TODO implement some integrity checks + # TODO implement some filesize restrictions return True + @staticmethod + def generate_password_hash(password: str) -> str: + """Generates a base64-encoded SHA512 hash of the input string. + + Parameters + ---------- + password : str + The plaintext password for which to generate a hash. + + Returns + ------- + str + A base64-encoded SHA512 hash digest of the input string. + + Raises + ------ + TypeError + If the input is not of type string. + ValueError + If the input string is empty. + """ + + if not isinstance(password, str): + raise TypeError(f"Expected a string but got: '{type(password)}'.") + if not len(password): + raise ValueError("Input string cannot be empty.") + + hash_bytes = hashlib.sha512() + hash_bytes.update(bytes(password, "UTF-8")) + return b64encode(hash_bytes.digest()).decode("ASCII") + + + def _get_default_picture() -> Image.Image: + """Returns the default user picture as a PIL Image object. + + Returns + ------- + PIL.Image.Image + The default user profile picture. + """ + + image_path = f"{current_app.static_folder}/assets/default_user_icon.jpg" + return Image.open(image_path) + + def __init__( self, username: str, password_hash: str, email: str, - first_name: str, last_name: str, display_name: str, - picture: Image, + first_name: str, last_name: str, display_name = None, + picture = None, ): if not User.is_valid_username(username): @@ -234,16 +282,25 @@ class User: raise ValueError(f"Not a valid email address: '{email}'.") self.email = email - for name in [first_name, last_name, display_name]: + for name in [first_name, last_name]: if not User.is_valid_person_name(name): raise ValueError(f"Not a valid name: '{name}'.") self.first_name = first_name self.last_name = last_name - self.display_name = display_name - if not User.is_valid_picture(picture): - raise ValueError(f"Not a valid image: '{picture}'.") - self.picture = picture + if display_name is not None: + if not User.is_valid_person_name(display_name): + raise ValueError(f"Not a valid display name: '{display_name}'.") + self.display_name = display_name + else: + self.display_name = first_name + + if picture is not None: + if not User.is_valid_picture(picture): + raise ValueError(f"Not a valid image: '{picture}'.") + self.picture = picture + else: + self.picture = User._get_default_picture() def __eq__(self, other):