From f5f7293ded7351a3e620bdfd0cd82976fd8d99f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Wed, 27 Mar 2024 10:26:24 +0100 Subject: [PATCH] refactor: Role has its own class --- poetry.lock | 31 ++++++++++++++-- pyproject.toml | 1 + web/b3desk/__init__.py | 7 ++++ web/b3desk/endpoints/api.py | 5 +-- web/b3desk/endpoints/join.py | 18 +++++----- web/b3desk/endpoints/meetings.py | 13 +++---- web/b3desk/models/bbb.py | 15 +++++--- web/b3desk/models/meetings.py | 35 ++++++++++++------- web/b3desk/templates/meeting/join.html | 8 ++--- .../templates/meeting/mailto/mail_body.txt | 10 +++--- .../templates/meeting/modals/invite.html | 16 ++++----- web/b3desk/templates/meeting/row.html | 14 ++++---- web/b3desk/templates/meeting/wait.html | 2 +- web/b3desk/utils.py | 23 +++++++++++- web/requirements.app.txt | 2 ++ web/requirements.doc.txt | 2 ++ web/tests/meeting/test_join.py | 16 +++++---- web/tests/meeting/test_meeting.py | 10 +++--- 18 files changed, 153 insertions(+), 75 deletions(-) diff --git a/poetry.lock b/poetry.lock index 677e5460..e0dad1a6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2112,6 +2112,23 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-slugify" +version = "8.0.4" +description = "A Python slugify application that also handles Unicode" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, + {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, +] + +[package.dependencies] +text-unidecode = ">=1.3" + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + [[package]] name = "pytz" version = "2024.1" @@ -2148,7 +2165,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2591,6 +2607,17 @@ test-all = ["Babel (>=1.3)", "Jinja2 (>=2.3)", "Pygments (>=1.2)", "arrow (>=0.3 timezone = ["python-dateutil"] url = ["furl (>=0.4.1)"] +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +optional = false +python-versions = "*" +files = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] + [[package]] name = "tomli" version = "2.0.1" @@ -2820,4 +2847,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "a4e1c89301fdd10b9c8635e9b51a8fe264c81aff40a4e23012e768d2084ef2d2" +content-hash = "6fe8da8eccc2e314cd36913a9b22c7996a659dc7efac8c028beafd95b9d56102" diff --git a/pyproject.toml b/pyproject.toml index bb03f3a6..9f697cb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ gunicorn = "21.2.0" netaddr = "^1.2.1" psycopg2 = "^2.9.3" pydantic-settings = "^2.1.0" +python-slugify = "^8.0.4" redis = "^5.0.1" requests = "^2.27.1" sqlalchemy = "^1.4.31" diff --git a/web/b3desk/__init__.py b/web/b3desk/__init__.py index 88130be9..59073d22 100644 --- a/web/b3desk/__init__.py +++ b/web/b3desk/__init__.py @@ -26,6 +26,7 @@ from b3desk.settings import MainSettings from b3desk.utils import is_rie +from .utils import enum_converter from .utils import model_converter __version__ = "0.0.0" @@ -109,6 +110,7 @@ def setup_database(app): def setup_jinja(app): + from b3desk.models.meetings import Role from b3desk.session import get_current_user from b3desk.session import has_user_session @@ -136,6 +138,7 @@ def global_processor(): "is_rie": is_rie(), "version": __version__, "LANGUAGES": LANGUAGES, + "Role": Role, **app.config["WORDINGS"], **session_dict, } @@ -144,11 +147,15 @@ def global_processor(): def setup_flask(app): with app.app_context(): from b3desk.models.meetings import Meeting + from b3desk.models.meetings import Role from b3desk.models.users import User for model in (Meeting, User): app.url_map.converters[model.__name__.lower()] = model_converter(model) + for enum in (Role,): + app.url_map.converters[enum.__name__.lower()] = enum_converter(enum) + def setup_error_pages(app): @app.errorhandler(400) diff --git a/web/b3desk/endpoints/api.py b/web/b3desk/endpoints/api.py index 1f61fc05..f96941dd 100644 --- a/web/b3desk/endpoints/api.py +++ b/web/b3desk/endpoints/api.py @@ -1,6 +1,7 @@ from flask import Blueprint from flask import request +from b3desk.models.meetings import Role from b3desk.models.users import get_or_create_user from .. import auth @@ -20,8 +21,8 @@ def api_meetings(): "meetings": [ { "name": meeting.name, - "moderator_url": meeting.get_signin_url("moderator"), - "attendee_url": meeting.get_signin_url("attendee"), + "moderator_url": meeting.get_signin_url(Role.moderator), + "attendee_url": meeting.get_signin_url(Role.attendee), } for meeting in user.meetings ] diff --git a/web/b3desk/endpoints/join.py b/web/b3desk/endpoints/join.py index d5bc8865..fc911e4b 100644 --- a/web/b3desk/endpoints/join.py +++ b/web/b3desk/endpoints/join.py @@ -14,6 +14,7 @@ from b3desk.forms import JoinMeetingForm from b3desk.models import db from b3desk.models.meetings import Meeting +from b3desk.models.meetings import Role from b3desk.models.meetings import get_mail_meeting from b3desk.models.meetings import get_meeting_from_meeting_id_and_user_id @@ -60,7 +61,7 @@ def signin_mail_meeting(meeting_fake_id, expiration, h): expiration=expiration, user_id="fakeuserId", h=h, - role="moderator", + role=Role.moderator, ) @@ -81,7 +82,7 @@ def signin_meeting(meeting_fake_id, creator, h): current_user_id = get_current_user().id if has_user_session() else None role = meeting.get_role(h, current_user_id) - if role == "authenticated": + if role == Role.authenticated: return redirect( url_for("join.join_meeting_as_authenticated", meeting_id=meeting_fake_id) ) @@ -162,7 +163,7 @@ def join_meeting(): current_user_id = get_current_user().id if has_user_session() else None role = meeting.get_role(h, current_user_id) fullname_suffix = form["fullname_suffix"].data - if role == "authenticated": + if role == Role.authenticated: fullname = get_authenticated_attendee_fullname() elif not role: return redirect(url_for("public.index")) @@ -210,7 +211,7 @@ def join_mail_meeting(): flash(_("Lien expiré"), "error") return redirect(url_for("public.index")) - return redirect(meeting.get_join_url("moderator", fullname, create=True)) + return redirect(meeting.get_join_url(Role.moderator, fullname, create=True)) # Cannot use a flask converter here because sometimes 'meeting_id' is a 'fake_id' @@ -218,7 +219,7 @@ def join_mail_meeting(): @auth.oidc_auth("attendee") def join_meeting_as_authenticated(meeting_id): meeting = db.session.get(Meeting, meeting_id) or abort(404) - role = "authenticated" + role = Role.authenticated fullname = get_authenticated_attendee_fullname() return redirect( url_for( @@ -231,11 +232,8 @@ def join_meeting_as_authenticated(meeting_id): ) -@bp.route("/meeting/join//") +@bp.route("/meeting/join//") @auth.oidc_auth("default") @meeting_owner_needed -def join_meeting_as_role(meeting, role, owner): - if role not in ("attendee", "moderator"): - abort(404) - +def join_meeting_as_role(meeting, role: Role, owner): return redirect(meeting.get_join_url(role, owner.fullname, create=True)) diff --git a/web/b3desk/endpoints/meetings.py b/web/b3desk/endpoints/meetings.py index 01340b7d..4502bf3e 100644 --- a/web/b3desk/endpoints/meetings.py +++ b/web/b3desk/endpoints/meetings.py @@ -25,6 +25,7 @@ from b3desk.forms import ShowMeetingForm from b3desk.models import db from b3desk.models.meetings import Meeting +from b3desk.models.meetings import Role from b3desk.models.meetings import get_quick_meeting_from_user_and_random_string from b3desk.models.users import User @@ -38,14 +39,14 @@ bp = Blueprint("meetings", __name__) -def meeting_mailto_params(meeting, role): - if role == "moderator": +def meeting_mailto_params(meeting, role: Role): + if role == Role.moderator: return render_template( - "meeting/mailto/mail_href.txt", meeting=meeting, role="moderator" + "meeting/mailto/mail_href.txt", meeting=meeting, role=role ).replace("\n", "%0D%0A") - elif role == "attendee": + elif role == Role.attendee: return render_template( - "meeting/mailto/mail_href.txt", meeting=meeting, role="attendee" + "meeting/mailto/mail_href.txt", meeting=meeting, role=role ).replace("\n", "%0D%0A") @@ -83,7 +84,7 @@ def quick_mail_meeting(): def quick_meeting(): user = get_current_user() meeting = get_quick_meeting_from_user_and_random_string(user) - return redirect(meeting.get_join_url("moderator", user.fullname, create=True)) + return redirect(meeting.get_join_url(Role.moderator, user.fullname, create=True)) @bp.route("/meeting/show/") diff --git a/web/b3desk/models/bbb.py b/web/b3desk/models/bbb.py index 21c0591a..ed347f00 100644 --- a/web/b3desk/models/bbb.py +++ b/web/b3desk/models/bbb.py @@ -86,6 +86,8 @@ def is_meeting_running(self): def create(self): """https://docs.bigbluebutton.org/development/api/#create""" + from .meetings import Role + params = { "meetingID": self.meeting.meetingID, "name": self.meeting.name, @@ -161,9 +163,9 @@ def create(self): "meeting/signin_links.html", moderator_message=self.meeting.moderatorOnlyMessage, moderator_link_introduction=quick_meeting_moderator_link_introduction, - moderator_signin_url=self.meeting.get_signin_url("moderator"), + moderator_signin_url=self.meeting.get_signin_url(Role.moderator), attendee_link_introduction=quick_meeting_attendee_link_introduction, - attendee_signin_url=self.meeting.get_signin_url("attendee"), + attendee_signin_url=self.meeting.get_signin_url(Role.attendee), ) params["guestPolicy"] = ( "ASK_MODERATOR" if self.meeting.guestPolicy else "ALWAYS_ACCEPT" @@ -267,17 +269,20 @@ def update_recordings(self, recording_ids, metadata): def prepare_request_to_join_bbb(self, meeting_role, fullname): """https://docs.bigbluebutton.org/dev/api.html#join""" + + from .meetings import Role + params = { "fullName": fullname, "meetingID": self.meeting.meetingID, "redirect": "true", } - if meeting_role == "attendee": + if meeting_role == Role.attendee: params["role"] = "viewer" params["guest"] = "true" - elif meeting_role == "authenticated": + elif meeting_role == Role.authenticated: params["role"] = "viewer" - elif meeting_role == "moderator": + elif meeting_role == Role.moderator: params["role"] = "moderator" return self.bbb_request("join", params=params) diff --git a/web/b3desk/models/meetings.py b/web/b3desk/models/meetings.py index 5a10f9c7..f0521687 100644 --- a/web/b3desk/models/meetings.py +++ b/web/b3desk/models/meetings.py @@ -11,6 +11,7 @@ import hashlib from datetime import datetime from datetime import timedelta +from enum import StrEnum from flask import current_app from flask import url_for @@ -25,6 +26,12 @@ MODERATOR_ONLY_MESSAGE_MAXLENGTH = 150 +class Role(StrEnum): + attendee = "attendee" + moderator = "moderator" + authenticated = "authenticated" + + class MeetingFiles(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.Unicode(4096)) @@ -154,7 +161,7 @@ def fake_id(self, fake_value): def fake_id(self): del self._fake_id - def get_hash(self, role): + def get_hash(self, role: Role): s = f"{self.meetingID}|{self.attendeePW}|{self.name}|{role}" return hashlib.sha1(s.encode("utf-8")).hexdigest() @@ -194,10 +201,12 @@ def update_recording_name(self, recording_id, name): recording_ids=[recording_id], metadata={"name": name} ) - def get_join_url(self, meeting_role, fullname, fullname_suffix="", create=False): + def get_join_url( + self, meeting_role: Role, fullname, fullname_suffix="", create=False + ): is_meeting_available = self.is_running() should_create_room = ( - not is_meeting_available and (meeting_role == "moderator") and create + not is_meeting_available and (meeting_role == Role.moderator) and create ) if should_create_room: data = self.create_bbb() @@ -219,7 +228,7 @@ def get_join_url(self, meeting_role, fullname, fullname_suffix="", create=False) fullname_suffix=fullname_suffix, ) - def get_signin_url(self, meeting_role): + def get_signin_url(self, meeting_role: Role): return url_for( "join.signin_meeting", meeting_fake_id=self.fake_id, @@ -247,18 +256,18 @@ def get_mail_signin_url(self): _external=True, ) - def get_role(self, hashed_role, user_id=None): + def get_role(self, hashed_role, user_id=None) -> Role | None: if user_id and self.user.id == user_id: - return "moderator" - elif self.get_hash("attendee") == hashed_role: - role = "attendee" - elif self.get_hash("moderator") == hashed_role: - role = "moderator" - elif self.get_hash("authenticated") == hashed_role: + return Role.moderator + elif self.get_hash(Role.attendee) == hashed_role: + role = Role.attendee + elif self.get_hash(Role.moderator) == hashed_role: + role = Role.moderator + elif self.get_hash(Role.authenticated) == hashed_role: role = ( - "authenticated" + Role.authenticated if current_app.config["OIDC_ATTENDEE_ENABLED"] - else "attendee" + else Role.attendee ) else: role = None diff --git a/web/b3desk/templates/meeting/join.html b/web/b3desk/templates/meeting/join.html index 61c3e275..f084ddf0 100644 --- a/web/b3desk/templates/meeting/join.html +++ b/web/b3desk/templates/meeting/join.html @@ -17,12 +17,12 @@

{% trans %}Rejoindre {{ the_meeting }}{% endtrans %}

- {% if role != "authenticated" %}

{% trans %}Vous pouvez également préciser votre service ou votre fonction.{% endtrans %}

{% endif %} + {% if role != Role.authenticated %}

{% trans %}Vous pouvez également préciser votre service ou votre fonction.{% endtrans %}

{% endif %}
- {% if role == "authenticated" %} + {% if role == Role.authenticated %}
@@ -39,7 +39,7 @@

{% trans %}Rejoindre {{ the_meeting }}{% endtrans %}

- {% if role != "authenticated" and not user %} + {% if role != Role.authenticated and not user %}
{% trans %}Vous êtes propriétaire de cette salle ?{% endtrans %} @@ -52,7 +52,7 @@

{% trans %}Rejoindre {{ the_meeting }}{% endtrans %}

-{% if role == "authenticated" %} +{% if role == Role.authenticated %}