Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow users to set custom profile pictures #2405

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
aa9a93a
Implemented feature: Allow users to set custom profile pictures
workaryangupta Jun 14, 2024
b7ceb1e
optional profile image
workaryangupta Jun 17, 2024
0742c39
flake8
workaryangupta Jun 17, 2024
f2cb29a
enhanced image upload security and validation
workaryangupta Jun 18, 2024
0fbad83
storing image path in DB instead of image itself
workaryangupta Jun 21, 2024
51f0ccb
set limit for profile image uploads
workaryangupta Jun 21, 2024
254e47e
added new config variable to mscolab init
workaryangupta Jun 21, 2024
e805e4c
Updated logic to store relative paths for user profile image instead …
workaryangupta Jul 1, 2024
89af2d0
Refactored upload_profile_image to use early returns
workaryangupta Jul 1, 2024
cefba82
Used create_files to ensure needed paths exist
workaryangupta Jul 1, 2024
9c15bf8
refactored directory separator slashes logic
workaryangupta Jul 2, 2024
9d49c6b
flake8
workaryangupta Jul 2, 2024
f4321d4
fixed failing tests and reduced def upload_file signature
workaryangupta Jul 3, 2024
ab514c4
flake8
workaryangupta Jul 3, 2024
0b68218
removed accidentally pushed testing logs
workaryangupta Jul 3, 2024
69a9ecc
fixed more failing tests since ext is no longer a part of uploaded fi…
workaryangupta Jul 3, 2024
57bbd5a
using fs instead of os while making dir in def upload_file
workaryangupta Jul 4, 2024
b56ab25
mscolab.py changes
workaryangupta Jul 5, 2024
e0a038a
refactored functions in server.py and file_manager.py
workaryangupta Jul 5, 2024
d27b876
removed PROFILE_IMG_FOLDER config var
workaryangupta Jul 5, 2024
3141566
removed pytest.log
workaryangupta Jul 5, 2024
ce0a390
delete old user pfp when new is uploaded
workaryangupta Jul 6, 2024
a646774
removed pytest log
workaryangupta Jul 6, 2024
67a28da
fixed error
workaryangupta Jul 6, 2024
0bfe0ec
removed truthy check for user options icon and added some ToDo
workaryangupta Jul 7, 2024
8ca40f5
spelling mistake
workaryangupta Jul 7, 2024
e532435
pytest log
workaryangupta Jul 7, 2024
3b3744c
added a ToDo for adding namespace for chat attachments
workaryangupta Jul 7, 2024
4e49277
added my name to authors
workaryangupta Jul 7, 2024
07b06b6
refactored fetch_profile_image route
workaryangupta Jul 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mslib/mscolab/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ class default_mscolab_settings:
# used to generate the password token
SECURITY_PASSWORD_SALT = secrets.token_urlsafe(16)

# Max allowed file size for uploading user profile image
MAX_IMAGE_SIZE = 1 * 1024 * 1024 # 1MB
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved

# Allowed file extensions for uploading user profile image
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved

STUB_CODE = """<?xml version="1.0" encoding="utf-8"?>
<FlightTrack version="1.7.6">
<ListOfWaypoints>
Expand Down
37 changes: 31 additions & 6 deletions mslib/mscolab/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ def wrapper(*args, **kwargs):
return wrapper


def allowed_file(filename):
""" Check if the uploaded file is in the allowed set of extensions """
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
return '.' in filename and filename.rsplit('.', 1)[1].lower() in mscolab_settings.ALLOWED_EXTENSIONS


def get_idp_entity_id(selected_idp):
"""
Finds the entity_id from the configured IDPs
Expand Down Expand Up @@ -352,17 +357,37 @@ def get_user():


@APP.route('/upload_profile_image', methods=["POST"])
@verify_user
def upload_profile_image():
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
user_id = request.form['user_id']
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
file = request.files['image']
if file:
success, message = fm.save_user_profile_image(user_id, file.read())
if success:
return jsonify({'message': message}), 200
if file and allowed_file(file.filename):
if file.mimetype.startswith('image/'):
try:
data = file.read()
if len(data) > mscolab_settings.MAX_IMAGE_SIZE:
return jsonify({'message': 'File too large'}), 413
success, message = fm.save_user_profile_image(user_id, data)
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
if success:
return jsonify({'message': message}), 200
else:
return jsonify({'message': message}), 400
except Exception as e:
return jsonify({'message': str(e)}), 500
else:
return jsonify({'message': message}), 400
return jsonify({'message': 'Invalid file type'}), 400
else:
return jsonify({'message': 'No file provided or invalid file type'}), 400


@APP.route('/fetch_profile_image', methods=["GET"])
def fetch_profile_image():
user_id = request.form['user_id']
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
image_data = fm.fetch_user_profile_image(user_id)
if image_data:
return Response(image_data, mimetype='image/png')
else:
return jsonify({'message': 'No file provided'}), 400
return 404
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved


@APP.route("/delete_own_account", methods=["POST"])
Expand Down
43 changes: 22 additions & 21 deletions mslib/msui/mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
from mslib.msui import mscolab_admin_window as maw
from mslib.msui import mscolab_version_history as mvh
from mslib.msui import socket_control as sc
from mslib.mscolab.file_manager import FileManager

import PyQt5
from PyQt5 import QtCore, QtGui, QtWidgets
Expand Down Expand Up @@ -699,25 +698,24 @@ def after_login(self, emailid, url, r):

def fetch_gravatar(self, refresh=False):
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
# Display custom profile picture if exists
from mslib.mscolab.server import APP
with APP.app_context():
fm = FileManager(APP.config["MSCOLAB_DATA_DIR"])
img_data = fm.fetch_user_profile_image(self.user["id"])
if img_data:
img_byte_arr = io.BytesIO(img_data)
pixmap = QPixmap()
pixmap.loadFromData(img_byte_arr.getvalue())

if hasattr(self, 'profile_dialog'):
self.profile_dialog.gravatarLabel.setPixmap(pixmap)

# set icon for user options toolbutton
icon = QtGui.QIcon()
icon.addPixmap(pixmap, QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.ui.userOptionsTb.setIcon(icon)
return
url = urljoin(self.mscolab_server_url, 'fetch_profile_image')
data = {'user_id': str(self.user["id"])}
response = requests.get(url, data=data)
if response.status_code == 200:
img_data = response.content
img_byte_arr = io.BytesIO(img_data)
pixmap = QPixmap()
pixmap.loadFromData(img_byte_arr.getvalue())
if hasattr(self, 'profile_dialog'):
self.profile_dialog.gravatarLabel.setPixmap(pixmap)
icon = QtGui.QIcon()
icon.addPixmap(pixmap, QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.ui.userOptionsTb.setIcon(icon)
else:
self.display_default_gravatar(refresh)

# Display default gravatar if custom pfp is not set
def display_default_gravatar(self, refresh):
# Display default gravatar if custom profile image is not set
email_hash = hashlib.md5(bytes(self.email.encode('utf-8')).lower()).hexdigest()
email_in_config = self.email in config_loader(dataset="gravatar_ids")
gravatar_img_path = fs.path.join(constants.GRAVATAR_DIR_PATH, f"{email_hash}.png")
Expand Down Expand Up @@ -835,7 +833,7 @@ def on_context_menu(point):
self.fetch_gravatar()

def upload_image(self):
file_name, _ = QFileDialog.getOpenFileName(self.prof_diag, "Open Image", "", "Image Files (*.png *.jpg *.bmp)")
file_name, _ = QFileDialog.getOpenFileName(self.prof_diag, "Open Image", "", "Image Files (*.png *.jpg *.jpeg)")
if file_name:
workaryangupta marked this conversation as resolved.
Show resolved Hide resolved
# Load, resize and display the image
image = Image.open(file_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs an try: except PIL.UnidentifiedImageError: because one can have wrong extensions used, e.g. renamed a mp4 to a jpg

currently this gives a traceback:

Fatal error in MSS 9.0.0 on Linux-6.5.0-10043-tuxedo-x86_64-with-glibc2.35
Python 3.11.6 | packaged by conda-forge | (main, Oct 3 2023, 10:40:35) [GCC 12.3.0]

Please report bugs in MSS to https://github.com/Open-MSS/MSS


Information about the fatal error:

Traceback (most recent call last):
  File "/home/user/PycharmProjects/2024/workaryangupta/MSS/mslib/msui/mscolab.py", line 850, in upload_image
    image = Image.open(file_name)
            ^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/Miniforge/envs/mssdev/lib/python3.11/site-packages/PIL/Image.py", line 3283, in open
    raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file '/home/user/tmp/1.jpg'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming a gif to a png and uploading works, so gif can maybe added to the list of extensions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming a gif to a jpg and uploading gives

Please report bugs in MSS to https://github.com/Open-MSS/MSS

Information about the fatal error:

Traceback (most recent call last):
  File "/home/user/Miniforge/envs/mssdev/lib/python3.11/site-packages/PIL/JpegImagePlugin.py", line 643, in _save
    rawmode = RAWMODE[im.mode]
              ~~~~~~~^^^^^^^^^
KeyError: 'P'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/user/PycharmProjects/2024/workaryangupta/MSS/mslib/msui/mscolab.py", line 853, in upload_image
    image.save(img_byte_arr, format=file_format)
  File "/home/user/Miniforge/envs/mssdev/lib/python3.11/site-packages/PIL/Image.py", line 2431, in save
    save_handler(self, fp, filename)
  File "/home/user/Miniforge/envs/mssdev/lib/python3.11/site-packages/PIL/JpegImagePlugin.py", line 646, in _save
    raise OSError(msg) from e
OSError: cannot write mode P as JPEG

So also catch OSError

Becasue this is not server code it is not critical, we help here the user and maybe get no issues when someone has files like this.

Expand All @@ -852,7 +850,10 @@ def upload_image(self):
# Prepare data for upload
img_byte_arr.seek(0)
files = {'image': ('profile_image.png', img_byte_arr, 'image/png')}
data = {'user_id': str(self.user["id"])}
data = {
"user_id": str(self.user["id"]),
"token": self.token
}

# Sending the request
try:
Expand Down
Loading