diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf9aaf73..8bb3f45b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,9 @@ repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.0.276' + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black rev: 22.12.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index fedbae14..d7a4e287 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,3 +58,10 @@ extend-exclude = ''' [tool.pytest.ini_options] env_files = ["web.env"] testpaths = "web" + +[tool.ruff] +ignore = [ + "E501", # line too long + "E722", # bare expect + "E402", # import not at the top of the file +] diff --git a/web/flaskr/__init__.py b/web/flaskr/__init__.py index 1a329cd3..4680f9dc 100755 --- a/web/flaskr/__init__.py +++ b/web/flaskr/__init__.py @@ -10,7 +10,7 @@ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. -from flask import Flask, render_template, request, session +from flask import Flask, request, session import os import logging @@ -57,6 +57,7 @@ def global_processor(): "config": app.config, "beta": app.config["BETA"], "documentation_link": app.config["DOCUMENTATION_LINK"], + "LANGUAGES": LANGUAGES, **app.config["WORDINGS"], } @@ -67,10 +68,6 @@ def global_processor(): csrf = CSRFProtect() csrf.init_app(app) - @app.context_processor - def global_processor(): - return {"LANGUAGES": LANGUAGES} - # init database with app.app_context(): import flaskr.routes @@ -79,7 +76,7 @@ def global_processor(): from .models import db db.init_app(app) - migrate = Migrate(app, db, compare_type=True) + Migrate(app, db, compare_type=True) # ensure the instance folder exists os.makedirs(app.instance_path, exist_ok=True) diff --git a/web/flaskr/forms.py b/web/flaskr/forms.py index 6518e280..1c6b2b87 100644 --- a/web/flaskr/forms.py +++ b/web/flaskr/forms.py @@ -7,7 +7,6 @@ SelectField, StringField, TextAreaField, - MultipleFileField, BooleanField, HiddenField, validators, diff --git a/web/flaskr/models.py b/web/flaskr/models.py index c7a383ef..3e3f9506 100755 --- a/web/flaskr/models.py +++ b/web/flaskr/models.py @@ -12,14 +12,9 @@ from flaskr.tasks import background_upload -import importlib.util -import sys -import signal - from flask_sqlalchemy import SQLAlchemy from sqlalchemy_utils import EncryptedType -from os import fork from flask import current_app, url_for, render_template @@ -29,6 +24,8 @@ from datetime import date, datetime, timedelta, timezone from flaskr.utils import secret_key +import os + db = SQLAlchemy() @@ -63,7 +60,7 @@ def get_user_nc_credentials(username): ) data = response.json() return data - except requests.exceptions.RequestException as e: + except requests.exceptions.RequestException: print("Cannot contact NC, returning None values") return {"nctoken": None, "nclocator": None, "nclogin": None} @@ -259,7 +256,6 @@ def insertDocsNoDefault(self): "%s/%s" % (BIGBLUEBUTTON_ENDPOINT, insertAction), params=params, ) - headers = {"Content-Type": "application/xml"} pr = request.prepare() bigbluebutton_secret = BIGBLUEBUTTON_SECRET s = "%s%s" % ( @@ -268,7 +264,7 @@ def insertDocsNoDefault(self): ) params["checksum"] = hashlib.sha1(s.encode("utf-8")).hexdigest() - r = requests.post( + requests.post( f"{BIGBLUEBUTTON_ENDPOINT}/{insertAction}", headers={"Content-Type": "application/xml"}, data=xml, @@ -415,7 +411,6 @@ def create(self): "%s/%s" % (BIGBLUEBUTTON_ENDPOINT, insertAction), params=params, ) - headers = {"Content-Type": "application/xml"} pr = request.prepare() bigbluebutton_secret = BIGBLUEBUTTON_SECRET s = "%s%s" % ( @@ -423,7 +418,7 @@ def create(self): bigbluebutton_secret, ) params["checksum"] = hashlib.sha1(s.encode("utf-8")).hexdigest() - task = background_upload.delay( + background_upload.delay( f"{BIGBLUEBUTTON_ENDPOINT}/{insertAction}", xml, params ) diff --git a/web/flaskr/routes.py b/web/flaskr/routes.py index ac74e5aa..9e6f8d2b 100755 --- a/web/flaskr/routes.py +++ b/web/flaskr/routes.py @@ -13,7 +13,7 @@ from werkzeug.utils import secure_filename from webdav3.client import Client as webdavClient -from webdav3.exceptions import MethodNotSupported, WebDavException +from webdav3.exceptions import WebDavException from pathlib import Path @@ -27,7 +27,6 @@ render_template, send_file, make_response, - Response, request, redirect, flash, @@ -35,7 +34,6 @@ jsonify, url_for, ) -from xml.etree import ElementTree from flask_babel import lazy_gettext from flask_pyoidc import OIDCAuthentication from flask_pyoidc.provider_configuration import ( @@ -43,7 +41,7 @@ ClientMetadata, ) from flask_pyoidc.user_session import UserSession -from sqlalchemy import or_, exc +from sqlalchemy import exc import random import hashlib import os @@ -51,14 +49,12 @@ import smtplib import string import requests -import shutil import secrets import uuid from datetime import datetime, date from email.mime.text import MIMEText from email.message import EmailMessage -from urllib.parse import quote from netaddr import IPNetwork, IPAddress from flaskr.forms import ( @@ -252,7 +248,7 @@ def get_meetings_stats(): stats_array = [row.split(",") for row in stats_array] participantCount = int(stats_array[current_app.config["STATS_INDEX"]][1]) runningCount = int(stats_array[current_app.config["STATS_INDEX"]][2]) - except Exception as e: + except Exception: return None result = {"participantCount": participantCount, "runningCount": runningCount} @@ -262,18 +258,15 @@ def get_meetings_stats(): @bp.route("/api/meetings", methods=["GET"]) @auth.token_auth(provider_name="default") def api_meetings(): - if auth.current_token_identity: - current_identity = auth.current_token_identity - else: + if not auth.current_token_identity: return redirect("/") + info = { "given_name": auth.current_token_identity["given_name"], "family_name": auth.current_token_identity["family_name"], "email": auth.current_token_identity["email"], } user = get_or_create_user(info) - fullname = user.fullname - stats = get_meetings_stats() return { "meetings": [ { @@ -292,7 +285,7 @@ def api_meetings(): def insertDocuments(meeting_id): from flask import request - user = get_current_user() + get_current_user() meeting = Meeting.query.get(meeting_id) files_title = request.get_json() secret_key = current_app.config["SECRET_KEY"] @@ -316,7 +309,6 @@ def insertDocuments(meeting_id): "%s/%s" % (current_app.config["BIGBLUEBUTTON_ENDPOINT"], "insertDocument"), params=params, ) - headers = {"Content-Type": "application/xml"} pr = request.prepare() bigbluebutton_secret = current_app.config["BIGBLUEBUTTON_SECRET"] s = "%s%s" % ( @@ -326,7 +318,7 @@ def insertDocuments(meeting_id): bigbluebutton_secret, ) params["checksum"] = hashlib.sha1(s.encode("utf-8")).hexdigest() - r = requests.post( + requests.post( f"{bbb_endpoint}/insertDocument", headers={"Content-Type": "application/xml"}, data=xml, @@ -482,7 +474,7 @@ def quick_mail_meeting(): id=email ) # this user can probably be removed if we created adock function m = get_quick_meeting_from_user_and_random_string(user) - signinurl = _send_mail(m, email) + _send_mail(m, email) flash( lazy_gettext("Vous avez reçu un courriel pour vous connecter"), "success_login" ) @@ -831,7 +823,7 @@ def add_meeting_file_dropzone(title, meeting_id, is_default): meetingFile.is_default = False meetingFile.save() - secret_key = current_app.config["SECRET_KEY"] + current_app.config["SECRET_KEY"] meetingFile.update() # file has been associated AND uploaded to nextcloud, we can safely remove it from visio-agent tmp directory removeDropzoneFile(dropzonePath) @@ -851,7 +843,7 @@ def add_meeting_file_dropzone(title, meeting_id, is_default): def add_meeting_file_URL(url, meeting_id, is_default): - user = get_current_user() + get_current_user() title = url.rsplit("/", 1)[-1] # test MAX_SIZE_UPLOAD for 20Mo @@ -880,7 +872,7 @@ def add_meeting_file_URL(url, meeting_id, is_default): meetingFile.url = url meetingFile.is_default = is_default - getFile = requests.get(url) + requests.get(url) try: meetingFile.save() @@ -911,7 +903,7 @@ def add_meeting_file_nextcloud(path, meeting_id, is_default): try: client = webdavClient(options) metadata = client.info(path) - except WebDavException as exception: + except WebDavException: user.disable_nextcloud() return jsonify( status=500, @@ -922,7 +914,7 @@ def add_meeting_file_nextcloud(path, meeting_id, is_default): return jsonify( status=500, isfrom="nextcloud", - msg=f"Fichier {title} TROP VOLUMINEUX, ne pas dépasser 20Mo", + msg=f"Fichier {path} TROP VOLUMINEUX, ne pas dépasser 20Mo", ) meetingFile = MeetingFiles() @@ -932,7 +924,7 @@ def add_meeting_file_nextcloud(path, meeting_id, is_default): meetingFile.meeting_id = meeting_id meetingFile.nc_path = path meetingFile.is_default = is_default - secret_key = current_app.config["SECRET_KEY"] + current_app.config["SECRET_KEY"] try: meetingFile.save() @@ -952,7 +944,7 @@ def add_meeting_file_nextcloud(path, meeting_id, is_default): def add_external_meeting_file_nextcloud(path, meeting_id): - user = get_current_user() + get_current_user() externalMeetingFile = MeetingFilesExternal() @@ -1123,7 +1115,7 @@ def save_meeting(): ) if meeting.is_meeting_running(): - end_meeting_form = EndMeetingForm() + EndMeetingForm() EndMeetingForm.meeting_id.data = meeting.id return render_template( "meeting/end.html", @@ -1200,7 +1192,7 @@ def insertDoc(token): # xml now use xml = f" " - r = requests.post( + requests.post( f"{current_app.config['BIGBLUEBUTTON_ENDPOINT']}/insertDocument", data=xml, headers=headers, @@ -1271,7 +1263,7 @@ def ncdownload(isexternal, mfid, mftoken): "local_path": tmpName, } client.download_sync(**kwargs) - except WebDavException as exception: + except WebDavException: meeting_file.meeting.user.disable_nextcloud() return jsonify(status=500, msg="La connexion avec Nextcloud semble rompue") # send the downloaded file to the BBB: @@ -1449,7 +1441,7 @@ def join_mail_meeting(): return redirect("/") fullname = form["fullname"].data meeting_fake_id = form["meeting_fake_id"].data - user_id = form["user_id"].data + form["user_id"].data expiration = form["expiration"].data h = form["h"].data @@ -1542,7 +1534,7 @@ def delete_meeting(): flash( "Nous n'avons pas pu supprimer les vidéos de cette " + current_app.config["WORDINGS"]["meeting_label"] - + " : {message}".format(code=return_code, message=message), + + " : {message}".format(message=message), "error", ) else: diff --git a/web/flaskr/tasks.py b/web/flaskr/tasks.py index d033b268..31a69e4f 100644 --- a/web/flaskr/tasks.py +++ b/web/flaskr/tasks.py @@ -1,5 +1,4 @@ import os -import time import requests from celery import Celery @@ -13,7 +12,7 @@ @celery.task(name="background_upload") def background_upload(endpoint, xml, params): - r = requests.post( + requests.post( endpoint, headers={"Content-Type": "application/xml"}, data=xml, diff --git a/web/migrations/alembic_helpers.py b/web/migrations/alembic_helpers.py index aa7729d3..73610726 100644 --- a/web/migrations/alembic_helpers.py +++ b/web/migrations/alembic_helpers.py @@ -1,7 +1,7 @@ # Code based on https://github.com/talkpython/data-driven-web-apps-with-flask from alembic import op -from sqlalchemy import engine_from_config, inspect +from sqlalchemy import engine_from_config from sqlalchemy import MetaData @@ -36,7 +36,7 @@ def table_does_not_exist(table): def table_has_column(table, column): has_column = False print(table, flush=True) - if not table in schema: + if table not in schema: return for s_column in schema[table]: print(" %s" % s_column, flush=True) diff --git a/web/migrations/versions/1094e771bd3f_create_meeting_files_table.py b/web/migrations/versions/1094e771bd3f_create_meeting_files_table.py index da7593b3..5e79f1cc 100644 --- a/web/migrations/versions/1094e771bd3f_create_meeting_files_table.py +++ b/web/migrations/versions/1094e771bd3f_create_meeting_files_table.py @@ -7,7 +7,6 @@ """ from alembic import op import sqlalchemy as sa -from sqlalchemy.dialects import postgresql import os import sys diff --git a/web/migrations/versions/65acbe9b0646_add_user_nextcloud_connection_info.py b/web/migrations/versions/65acbe9b0646_add_user_nextcloud_connection_info.py index 55cf8df5..28862650 100644 --- a/web/migrations/versions/65acbe9b0646_add_user_nextcloud_connection_info.py +++ b/web/migrations/versions/65acbe9b0646_add_user_nextcloud_connection_info.py @@ -7,7 +7,6 @@ """ from alembic import op import sqlalchemy as sa -from sqlalchemy.dialects import postgresql import os import sys diff --git a/web/migrations/versions/7d80b9223a1e_guestpolicy_migration.py b/web/migrations/versions/7d80b9223a1e_guestpolicy_migration.py index 362af05c..4f859ef1 100644 --- a/web/migrations/versions/7d80b9223a1e_guestpolicy_migration.py +++ b/web/migrations/versions/7d80b9223a1e_guestpolicy_migration.py @@ -7,7 +7,6 @@ """ from alembic import op import sqlalchemy as sa -from sqlalchemy.dialects import postgresql import os import sys diff --git a/web/migrations/versions/8fe077ecfb10_add_last_connection_utc_datetime.py b/web/migrations/versions/8fe077ecfb10_add_last_connection_utc_datetime.py index a1d7f9db..3f1feb94 100644 --- a/web/migrations/versions/8fe077ecfb10_add_last_connection_utc_datetime.py +++ b/web/migrations/versions/8fe077ecfb10_add_last_connection_utc_datetime.py @@ -7,7 +7,6 @@ """ from alembic import op import sqlalchemy as sa -from sqlalchemy.dialects import postgresql import os import sys diff --git a/web/migrations/versions/9aac3b5e1582_welcome_message_unbound.py b/web/migrations/versions/9aac3b5e1582_welcome_message_unbound.py index 447e5a57..cc30e97b 100644 --- a/web/migrations/versions/9aac3b5e1582_welcome_message_unbound.py +++ b/web/migrations/versions/9aac3b5e1582_welcome_message_unbound.py @@ -7,7 +7,6 @@ """ from alembic import op import sqlalchemy as sa -from sqlalchemy.dialects import postgresql import os import sys @@ -16,8 +15,6 @@ parent = os.path.dirname(current) sys.path.append(parent) -import alembic_helpers - # revision identifiers, used by Alembic. revision = "9aac3b5e1582" diff --git a/web/misc/gunicorn.py b/web/misc/gunicorn.py index 475be7fa..a6aadd95 100644 --- a/web/misc/gunicorn.py +++ b/web/misc/gunicorn.py @@ -203,7 +203,9 @@ def when_ready(server): def worker_int(worker): worker.log.info("worker received INT or QUIT signal") ## get traceback info - import threading, sys, traceback + import threading + import sys + import traceback id2name = {th.ident: th.name for th in threading.enumerate()} code = [] diff --git a/web/tests/conftest.py b/web/tests/conftest.py index 25e46745..26048fc5 100644 --- a/web/tests/conftest.py +++ b/web/tests/conftest.py @@ -2,7 +2,6 @@ import time import pytest -from flask import session from flaskr import create_app from flask_migrate import Migrate from flask_webtest import TestApp @@ -57,7 +56,7 @@ def app(mocker): } ) with app.app_context(): - migrate = Migrate(app, db, compare_type=True) + Migrate(app, db, compare_type=True) db.create_all() return app diff --git a/web/tests/meeting/test_join.py b/web/tests/meeting/test_join.py index fbb2ebd6..6355ec38 100644 --- a/web/tests/meeting/test_join.py +++ b/web/tests/meeting/test_join.py @@ -62,22 +62,14 @@ def test_auth_attendee_disabled(client_app, app, meeting): the attendee authentication step. https://github.com/numerique-gouv/b3desk/issues/9 """ - # TODO: refactor this test with modern test conventions when #6 is merged - app.config["OIDC_ATTENDEE_ENABLED"] = False + meeting_hash = meeting.get_hash("authenticated") - with app.app_context(): - user_id = 1 - meeting = Meeting.query.get(1) - meeting_id = meeting.id - meeting_hash = meeting.get_hash("authenticated") - - url = f"/meeting/signin/{meeting_id}/creator/{user_id}/hash/{meeting_hash}" - response = client_app.get(url) - - assert response.status_code == 200 - form_action_url = "/meeting/join" - assert form_action_url in response.data.decode() + url = f"/meeting/signin/{meeting.id}/creator/{meeting.user.id}/hash/{meeting_hash}" + response = client_app.get( + url, extra_environ={"REMOTE_ADDR": "127.0.0.1"}, status=200 + ) + response.mustcontain("/meeting/join") def test_join_meeting_as_authenticated_attendee( @@ -198,7 +190,7 @@ def test_join_meeting_as_role( def test_join_meeting_as_role__meeting_not_found( client_app, app, authenticated_user, bbb_response ): - client_app.get(f"/meeting/join/321/attendee", status=404) + client_app.get("/meeting/join/321/attendee", status=404) def test_join_meeting_as_role__not_attendee_or_moderator( diff --git a/web/tests/meeting/test_recordings.py b/web/tests/meeting/test_recordings.py index 59786f39..4b9711f5 100644 --- a/web/tests/meeting/test_recordings.py +++ b/web/tests/meeting/test_recordings.py @@ -2,8 +2,6 @@ import pytest -from flaskr.models import Meeting - @pytest.fixture() def bbb_getRecordings_response(mocker): diff --git a/web/tests/test_default.py b/web/tests/test_default.py index c976dd11..82413e44 100644 --- a/web/tests/test_default.py +++ b/web/tests/test_default.py @@ -39,15 +39,15 @@ def test_home__authenticated_user(client_app, mocker, authenticated_user): def test_change_language(client_app): - response = client_app.get("/faq?lang=fr", status=200) + client_app.get("/faq?lang=fr", status=200) with client_app.session_transaction() as session: assert session["lang"] == "fr" - response = client_app.get("/faq?lang=uk", status=200) + client_app.get("/faq?lang=uk", status=200) with client_app.session_transaction() as session: assert session["lang"] == "uk" - response = client_app.get("/faq", status=200) + client_app.get("/faq", status=200) with client_app.session_transaction() as session: assert session["lang"] == "uk" diff --git a/web/tests/test_utils.py b/web/tests/test_utils.py index 6cb9338f..5126e197 100644 --- a/web/tests/test_utils.py +++ b/web/tests/test_utils.py @@ -7,20 +7,20 @@ def test_retry_join_meeting(): assert ( flaskr.utils.retry_join_meeting(WAIT_ROOM_URL, "authenticated", "Alice", "") - == True + is True ) - assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "attendee", "", "") == False - assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "attendee", "Alice", "") == True - assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "moderator", "", "") == False - assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "moderator", "Alice", "") == True - assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "authenticated", "", "") == False + assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "attendee", "", "") is False + assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "attendee", "Alice", "") is True + assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "moderator", "", "") is False + assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "moderator", "Alice", "") is True + assert flaskr.utils.retry_join_meeting(SIGNIN_URL, "authenticated", "", "") is False assert ( flaskr.utils.retry_join_meeting(SIGNIN_URL, "authenticated", "Alice", "") - == False + is False ) assert ( flaskr.utils.retry_join_meeting( SIGNIN_URL, "authenticated", "Alice", "Service A" ) - == True + is True )