diff --git a/app.py b/app.py index d0a5a1ad0166a949335494d9a8c5590c70a9cecb..1f1654cc0d5fcd4e496b73bc783f380b121258a3 100644 --- a/app.py +++ b/app.py @@ -2,14 +2,11 @@ import os import secrets from flask import Flask, render_template, request, redirect, url_for, send_from_directory, abort from flask_sqlalchemy import SQLAlchemy -from flask_wtf import FlaskForm -from wtforms import StringField, SubmitField app = Flask(__name__, static_folder='static') app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' app.config['SECRET_KEY'] = secrets.token_hex(16) -app.config['WTF_CSRF_ENABLED'] = True db = SQLAlchemy(app) @@ -18,11 +15,6 @@ class Project(db.Model): title = db.Column(db.String(100), nullable=False) description = db.Column(db.Text, nullable=False) -class AddProjectForm(FlaskForm): - title = StringField('Title') - description = StringField('Description') - submit = SubmitField('Submit') - @app.route('/') def home(): try: @@ -54,21 +46,23 @@ def portfolio(): def contact(): return render_template('contact.html') -# Updated route for adding a project with Flask-WTF form +# Updated route for adding a project without Flask-WTF form @app.route('/add_project', methods=['GET', 'POST']) def add_project(): - form = AddProjectForm() + if request.method == 'POST': + # Retrieve form data directly from request + title = request.form.get('title') + description = request.form.get('description') - if form.validate_on_submit(): # Print or log the form data to check if it's received - print(f"Received form data - Title: {form.title.data}, Description: {form.description.data}") + print(f"Received form data - Title: {title}, Description: {description}") - new_project = Project(title=form.title.data, description=form.description.data) + new_project = Project(title=title, description=description) db.session.add(new_project) db.session.commit() return redirect(url_for('home')) - return render_template('add_project.html', form=form) + return render_template('add_project.html') # Updated route for serving the 'my-cv.docx' file @app.route('/download_cv') @@ -92,5 +86,3 @@ def download_assessment(filename): app.logger.exception(f"Error serving assessment file: {str(e)}") abort(500) -if __name__ == '__main__': - app.run(debug=True, port=int(os.environ.get('PORT', 5000))) \ No newline at end of file diff --git a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/INSTALLER b/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/INSTALLER deleted file mode 100644 index a1b589e38a32041e49332e5e81c2d363dc418d68..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/METADATA b/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/METADATA deleted file mode 100644 index 92f1ff25658838ae9639052003770899dc3851cb..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/METADATA +++ /dev/null @@ -1,72 +0,0 @@ -Metadata-Version: 2.1 -Name: Flask-WTF -Version: 1.2.1 -Summary: Form rendering, validation, and CSRF protection for Flask with WTForms. -Project-URL: Documentation, https://flask-wtf.readthedocs.io/ -Project-URL: Changes, https://flask-wtf.readthedocs.io/changes/ -Project-URL: Source Code, https://github.com/wtforms/flask-wtf/ -Project-URL: Issue Tracker, https://github.com/wtforms/flask-wtf/issues/ -Project-URL: Chat, https://discord.gg/pallets -Maintainer: WTForms -License: Copyright 2010 WTForms - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -License-File: LICENSE.rst -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application -Classifier: Topic :: Software Development :: Libraries :: Application Frameworks -Requires-Python: >=3.8 -Requires-Dist: flask -Requires-Dist: itsdangerous -Requires-Dist: wtforms -Provides-Extra: email -Requires-Dist: email-validator; extra == 'email' -Description-Content-Type: text/x-rst - -Flask-WTF -========= - -Simple integration of Flask and WTForms, including CSRF, file upload, -and reCAPTCHA. - -Links ------ - -- Documentation: https://flask-wtf.readthedocs.io/ -- Changes: https://flask-wtf.readthedocs.io/changes/ -- PyPI Releases: https://pypi.org/project/Flask-WTF/ -- Source Code: https://github.com/wtforms/flask-wtf/ -- Issue Tracker: https://github.com/wtforms/flask-wtf/issues/ -- Chat: https://discord.gg/pallets diff --git a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/RECORD b/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/RECORD deleted file mode 100644 index a7814665f525f142935e652b0eafe2213194eccf..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/RECORD +++ /dev/null @@ -1,26 +0,0 @@ -flask_wtf-1.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -flask_wtf-1.2.1.dist-info/METADATA,sha256=9Y5upDJ7WU2m2l4erWImF3HcVSWIZKH3TdX6klYpq4M,3373 -flask_wtf-1.2.1.dist-info/RECORD,, -flask_wtf-1.2.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -flask_wtf-1.2.1.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87 -flask_wtf-1.2.1.dist-info/licenses/LICENSE.rst,sha256=1fGQNkUVeMs27u8EyZ6_fXyi5w3PBDY2UZvEIOFafGI,1475 -flask_wtf/__init__.py,sha256=x6ydw5SJzsXZgz-Y6IM_95Sy8VufRepvZH1DUIlFoTo,214 -flask_wtf/__pycache__/__init__.cpython-311.pyc,, -flask_wtf/__pycache__/_compat.cpython-311.pyc,, -flask_wtf/__pycache__/csrf.cpython-311.pyc,, -flask_wtf/__pycache__/file.cpython-311.pyc,, -flask_wtf/__pycache__/form.cpython-311.pyc,, -flask_wtf/__pycache__/i18n.cpython-311.pyc,, -flask_wtf/_compat.py,sha256=N3sqC9yzFWY-3MZ7QazX1sidvkO3d5yy4NR6lkp0s94,248 -flask_wtf/csrf.py,sha256=O-fjnWygxxi_FsIU2koua97ZpIhiOJVDHA57dXLpvTA,10171 -flask_wtf/file.py,sha256=AsfkYTCgtqGWySimc_NjeAxg-DtpdcthhqMLrXIDAhU,4706 -flask_wtf/form.py,sha256=TmR7xCrxin2LHp6thn7fq1OeU8aLB7xsZzvv52nH7Ss,4049 -flask_wtf/i18n.py,sha256=TyO8gqt9DocHMSaNhj0KKgxoUrPYs-G1nVW-jns0SOw,1166 -flask_wtf/recaptcha/__init__.py,sha256=m4eNGoU3Q0Wnt_wP8VvOlA0mwWuoMtAcK9pYT7sPFp8,106 -flask_wtf/recaptcha/__pycache__/__init__.cpython-311.pyc,, -flask_wtf/recaptcha/__pycache__/fields.cpython-311.pyc,, -flask_wtf/recaptcha/__pycache__/validators.cpython-311.pyc,, -flask_wtf/recaptcha/__pycache__/widgets.cpython-311.pyc,, -flask_wtf/recaptcha/fields.py,sha256=M1-RFuUKOsJAzsLm3xaaxuhX2bB9oRqS-HVSN-NpkmI,433 -flask_wtf/recaptcha/validators.py,sha256=3sd1mUQT3Y3D_WJeKwecxUGstnhh_QD-A_dEBJfkf6s,2434 -flask_wtf/recaptcha/widgets.py,sha256=J_XyxAZt3uB15diIMnkXXGII2dmsWCsVsKV3KQYn4Ns,1512 diff --git a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/REQUESTED b/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/REQUESTED deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/WHEEL b/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/WHEEL deleted file mode 100644 index ba1a8af28bcccdacebb8c22dfda1537447a1a58a..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: hatchling 1.18.0 -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/licenses/LICENSE.rst b/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/licenses/LICENSE.rst deleted file mode 100644 index 63c3617a2d7164d30cae358c23eb3f75b5a758a1..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf-1.2.1.dist-info/licenses/LICENSE.rst +++ /dev/null @@ -1,28 +0,0 @@ -Copyright 2010 WTForms - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/venv/Lib/site-packages/flask_wtf/__init__.py b/venv/Lib/site-packages/flask_wtf/__init__.py deleted file mode 100644 index be2649e26d8dfa2cde5457f13b72715135d12b5a..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .csrf import CSRFProtect -from .form import FlaskForm -from .form import Form -from .recaptcha import Recaptcha -from .recaptcha import RecaptchaField -from .recaptcha import RecaptchaWidget - -__version__ = "1.2.1" diff --git a/venv/Lib/site-packages/flask_wtf/__pycache__/__init__.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 304531c8dd034ce8289de1e379acd11a66bf38cd..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/__pycache__/_compat.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/__pycache__/_compat.cpython-311.pyc deleted file mode 100644 index beb00be94d82d4c7316bfcf1139bf843f1194cc5..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/__pycache__/_compat.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/__pycache__/csrf.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/__pycache__/csrf.cpython-311.pyc deleted file mode 100644 index 3a6e9a34361fdea3a9249ed18fe09ac5474f45b6..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/__pycache__/csrf.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/__pycache__/file.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/__pycache__/file.cpython-311.pyc deleted file mode 100644 index bac198e45ca285c998a6e60eec1a57deacb716eb..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/__pycache__/file.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/__pycache__/form.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/__pycache__/form.cpython-311.pyc deleted file mode 100644 index 156609e14de7e83103f2a2c776a0a668e0e6b194..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/__pycache__/form.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/__pycache__/i18n.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/__pycache__/i18n.cpython-311.pyc deleted file mode 100644 index 04d2d7e2cbe3df7f61e7c03d89b95925494f4bec..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/__pycache__/i18n.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/_compat.py b/venv/Lib/site-packages/flask_wtf/_compat.py deleted file mode 100644 index 50973e063bbdbd6982fc9501221603efbc2e88f9..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/_compat.py +++ /dev/null @@ -1,11 +0,0 @@ -import warnings - - -class FlaskWTFDeprecationWarning(DeprecationWarning): - pass - - -warnings.simplefilter("always", FlaskWTFDeprecationWarning) -warnings.filterwarnings( - "ignore", category=FlaskWTFDeprecationWarning, module="wtforms|flask_wtf" -) diff --git a/venv/Lib/site-packages/flask_wtf/csrf.py b/venv/Lib/site-packages/flask_wtf/csrf.py deleted file mode 100644 index 06afa0cd4ef3670ca3357d47bbecc2baa7e18fe0..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/csrf.py +++ /dev/null @@ -1,329 +0,0 @@ -import hashlib -import hmac -import logging -import os -from urllib.parse import urlparse - -from flask import Blueprint -from flask import current_app -from flask import g -from flask import request -from flask import session -from itsdangerous import BadData -from itsdangerous import SignatureExpired -from itsdangerous import URLSafeTimedSerializer -from werkzeug.exceptions import BadRequest -from wtforms import ValidationError -from wtforms.csrf.core import CSRF - -__all__ = ("generate_csrf", "validate_csrf", "CSRFProtect") -logger = logging.getLogger(__name__) - - -def generate_csrf(secret_key=None, token_key=None): - """Generate a CSRF token. The token is cached for a request, so multiple - calls to this function will generate the same token. - - During testing, it might be useful to access the signed token in - ``g.csrf_token`` and the raw token in ``session['csrf_token']``. - - :param secret_key: Used to securely sign the token. Default is - ``WTF_CSRF_SECRET_KEY`` or ``SECRET_KEY``. - :param token_key: Key where token is stored in session for comparison. - Default is ``WTF_CSRF_FIELD_NAME`` or ``'csrf_token'``. - """ - - secret_key = _get_config( - secret_key, - "WTF_CSRF_SECRET_KEY", - current_app.secret_key, - message="A secret key is required to use CSRF.", - ) - field_name = _get_config( - token_key, - "WTF_CSRF_FIELD_NAME", - "csrf_token", - message="A field name is required to use CSRF.", - ) - - if field_name not in g: - s = URLSafeTimedSerializer(secret_key, salt="wtf-csrf-token") - - if field_name not in session: - session[field_name] = hashlib.sha1(os.urandom(64)).hexdigest() - - try: - token = s.dumps(session[field_name]) - except TypeError: - session[field_name] = hashlib.sha1(os.urandom(64)).hexdigest() - token = s.dumps(session[field_name]) - - setattr(g, field_name, token) - - return g.get(field_name) - - -def validate_csrf(data, secret_key=None, time_limit=None, token_key=None): - """Check if the given data is a valid CSRF token. This compares the given - signed token to the one stored in the session. - - :param data: The signed CSRF token to be checked. - :param secret_key: Used to securely sign the token. Default is - ``WTF_CSRF_SECRET_KEY`` or ``SECRET_KEY``. - :param time_limit: Number of seconds that the token is valid. Default is - ``WTF_CSRF_TIME_LIMIT`` or 3600 seconds (60 minutes). - :param token_key: Key where token is stored in session for comparison. - Default is ``WTF_CSRF_FIELD_NAME`` or ``'csrf_token'``. - - :raises ValidationError: Contains the reason that validation failed. - - .. versionchanged:: 0.14 - Raises ``ValidationError`` with a specific error message rather than - returning ``True`` or ``False``. - """ - - secret_key = _get_config( - secret_key, - "WTF_CSRF_SECRET_KEY", - current_app.secret_key, - message="A secret key is required to use CSRF.", - ) - field_name = _get_config( - token_key, - "WTF_CSRF_FIELD_NAME", - "csrf_token", - message="A field name is required to use CSRF.", - ) - time_limit = _get_config(time_limit, "WTF_CSRF_TIME_LIMIT", 3600, required=False) - - if not data: - raise ValidationError("The CSRF token is missing.") - - if field_name not in session: - raise ValidationError("The CSRF session token is missing.") - - s = URLSafeTimedSerializer(secret_key, salt="wtf-csrf-token") - - try: - token = s.loads(data, max_age=time_limit) - except SignatureExpired as e: - raise ValidationError("The CSRF token has expired.") from e - except BadData as e: - raise ValidationError("The CSRF token is invalid.") from e - - if not hmac.compare_digest(session[field_name], token): - raise ValidationError("The CSRF tokens do not match.") - - -def _get_config( - value, config_name, default=None, required=True, message="CSRF is not configured." -): - """Find config value based on provided value, Flask config, and default - value. - - :param value: already provided config value - :param config_name: Flask ``config`` key - :param default: default value if not provided or configured - :param required: whether the value must not be ``None`` - :param message: error message if required config is not found - :raises KeyError: if required config is not found - """ - - if value is None: - value = current_app.config.get(config_name, default) - - if required and value is None: - raise RuntimeError(message) - - return value - - -class _FlaskFormCSRF(CSRF): - def setup_form(self, form): - self.meta = form.meta - return super().setup_form(form) - - def generate_csrf_token(self, csrf_token_field): - return generate_csrf( - secret_key=self.meta.csrf_secret, token_key=self.meta.csrf_field_name - ) - - def validate_csrf_token(self, form, field): - if g.get("csrf_valid", False): - # already validated by CSRFProtect - return - - try: - validate_csrf( - field.data, - self.meta.csrf_secret, - self.meta.csrf_time_limit, - self.meta.csrf_field_name, - ) - except ValidationError as e: - logger.info(e.args[0]) - raise - - -class CSRFProtect: - """Enable CSRF protection globally for a Flask app. - - :: - - app = Flask(__name__) - csrf = CSRFProtect(app) - - Checks the ``csrf_token`` field sent with forms, or the ``X-CSRFToken`` - header sent with JavaScript requests. Render the token in templates using - ``{{ csrf_token() }}``. - - See the :ref:`csrf` documentation. - """ - - def __init__(self, app=None): - self._exempt_views = set() - self._exempt_blueprints = set() - - if app: - self.init_app(app) - - def init_app(self, app): - app.extensions["csrf"] = self - - app.config.setdefault("WTF_CSRF_ENABLED", True) - app.config.setdefault("WTF_CSRF_CHECK_DEFAULT", True) - app.config["WTF_CSRF_METHODS"] = set( - app.config.get("WTF_CSRF_METHODS", ["POST", "PUT", "PATCH", "DELETE"]) - ) - app.config.setdefault("WTF_CSRF_FIELD_NAME", "csrf_token") - app.config.setdefault("WTF_CSRF_HEADERS", ["X-CSRFToken", "X-CSRF-Token"]) - app.config.setdefault("WTF_CSRF_TIME_LIMIT", 3600) - app.config.setdefault("WTF_CSRF_SSL_STRICT", True) - - app.jinja_env.globals["csrf_token"] = generate_csrf - app.context_processor(lambda: {"csrf_token": generate_csrf}) - - @app.before_request - def csrf_protect(): - if not app.config["WTF_CSRF_ENABLED"]: - return - - if not app.config["WTF_CSRF_CHECK_DEFAULT"]: - return - - if request.method not in app.config["WTF_CSRF_METHODS"]: - return - - if not request.endpoint: - return - - if app.blueprints.get(request.blueprint) in self._exempt_blueprints: - return - - view = app.view_functions.get(request.endpoint) - dest = f"{view.__module__}.{view.__name__}" - - if dest in self._exempt_views: - return - - self.protect() - - def _get_csrf_token(self): - # find the token in the form data - field_name = current_app.config["WTF_CSRF_FIELD_NAME"] - base_token = request.form.get(field_name) - - if base_token: - return base_token - - # if the form has a prefix, the name will be {prefix}-csrf_token - for key in request.form: - if key.endswith(field_name): - csrf_token = request.form[key] - - if csrf_token: - return csrf_token - - # find the token in the headers - for header_name in current_app.config["WTF_CSRF_HEADERS"]: - csrf_token = request.headers.get(header_name) - - if csrf_token: - return csrf_token - - return None - - def protect(self): - if request.method not in current_app.config["WTF_CSRF_METHODS"]: - return - - try: - validate_csrf(self._get_csrf_token()) - except ValidationError as e: - logger.info(e.args[0]) - self._error_response(e.args[0]) - - if request.is_secure and current_app.config["WTF_CSRF_SSL_STRICT"]: - if not request.referrer: - self._error_response("The referrer header is missing.") - - good_referrer = f"https://{request.host}/" - - if not same_origin(request.referrer, good_referrer): - self._error_response("The referrer does not match the host.") - - g.csrf_valid = True # mark this request as CSRF valid - - def exempt(self, view): - """Mark a view or blueprint to be excluded from CSRF protection. - - :: - - @app.route('/some-view', methods=['POST']) - @csrf.exempt - def some_view(): - ... - - :: - - bp = Blueprint(...) - csrf.exempt(bp) - - """ - - if isinstance(view, Blueprint): - self._exempt_blueprints.add(view) - return view - - if isinstance(view, str): - view_location = view - else: - view_location = ".".join((view.__module__, view.__name__)) - - self._exempt_views.add(view_location) - return view - - def _error_response(self, reason): - raise CSRFError(reason) - - -class CSRFError(BadRequest): - """Raise if the client sends invalid CSRF data with the request. - - Generates a 400 Bad Request response with the failure reason by default. - Customize the response by registering a handler with - :meth:`flask.Flask.errorhandler`. - """ - - description = "CSRF validation failed." - - -def same_origin(current_uri, compare_uri): - current = urlparse(current_uri) - compare = urlparse(compare_uri) - - return ( - current.scheme == compare.scheme - and current.hostname == compare.hostname - and current.port == compare.port - ) diff --git a/venv/Lib/site-packages/flask_wtf/file.py b/venv/Lib/site-packages/flask_wtf/file.py deleted file mode 100644 index a720dff8d81911df179e80512caa0056a47be410..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/file.py +++ /dev/null @@ -1,147 +0,0 @@ -from collections import abc - -from werkzeug.datastructures import FileStorage -from wtforms import FileField as _FileField -from wtforms import MultipleFileField as _MultipleFileField -from wtforms.validators import DataRequired -from wtforms.validators import StopValidation -from wtforms.validators import ValidationError - - -class FileField(_FileField): - """Werkzeug-aware subclass of :class:`wtforms.fields.FileField`.""" - - def process_formdata(self, valuelist): - valuelist = (x for x in valuelist if isinstance(x, FileStorage) and x) - data = next(valuelist, None) - - if data is not None: - self.data = data - else: - self.raw_data = () - - -class MultipleFileField(_MultipleFileField): - """Werkzeug-aware subclass of :class:`wtforms.fields.MultipleFileField`. - - .. versionadded:: 1.2.0 - """ - - def process_formdata(self, valuelist): - valuelist = (x for x in valuelist if isinstance(x, FileStorage) and x) - data = list(valuelist) or None - - if data is not None: - self.data = data - else: - self.raw_data = () - - -class FileRequired(DataRequired): - """Validates that the uploaded files(s) is a Werkzeug - :class:`~werkzeug.datastructures.FileStorage` object. - - :param message: error message - - You can also use the synonym ``file_required``. - """ - - def __call__(self, form, field): - field_data = [field.data] if not isinstance(field.data, list) else field.data - if not ( - all(isinstance(x, FileStorage) and x for x in field_data) and field_data - ): - raise StopValidation( - self.message or field.gettext("This field is required.") - ) - - -file_required = FileRequired - - -class FileAllowed: - """Validates that the uploaded file(s) is allowed by a given list of - extensions or a Flask-Uploads :class:`~flaskext.uploads.UploadSet`. - - :param upload_set: A list of extensions or an - :class:`~flaskext.uploads.UploadSet` - :param message: error message - - You can also use the synonym ``file_allowed``. - """ - - def __init__(self, upload_set, message=None): - self.upload_set = upload_set - self.message = message - - def __call__(self, form, field): - field_data = [field.data] if not isinstance(field.data, list) else field.data - if not ( - all(isinstance(x, FileStorage) and x for x in field_data) and field_data - ): - return - - filenames = [f.filename.lower() for f in field_data] - - for filename in filenames: - if isinstance(self.upload_set, abc.Iterable): - if any(filename.endswith("." + x) for x in self.upload_set): - continue - - raise StopValidation( - self.message - or field.gettext( - "File does not have an approved extension: {extensions}" - ).format(extensions=", ".join(self.upload_set)) - ) - - if not self.upload_set.file_allowed(field_data, filename): - raise StopValidation( - self.message - or field.gettext("File does not have an approved extension.") - ) - - -file_allowed = FileAllowed - - -class FileSize: - """Validates that the uploaded file(s) is within a minimum and maximum - file size (set in bytes). - - :param min_size: minimum allowed file size (in bytes). Defaults to 0 bytes. - :param max_size: maximum allowed file size (in bytes). - :param message: error message - - You can also use the synonym ``file_size``. - """ - - def __init__(self, max_size, min_size=0, message=None): - self.min_size = min_size - self.max_size = max_size - self.message = message - - def __call__(self, form, field): - field_data = [field.data] if not isinstance(field.data, list) else field.data - if not ( - all(isinstance(x, FileStorage) and x for x in field_data) and field_data - ): - return - - for f in field_data: - file_size = len(f.read()) - f.seek(0) # reset cursor position to beginning of file - - if (file_size < self.min_size) or (file_size > self.max_size): - # the file is too small or too big => validation failure - raise ValidationError( - self.message - or field.gettext( - "File must be between {min_size} and {max_size} bytes.".format( - min_size=self.min_size, max_size=self.max_size - ) - ) - ) - - -file_size = FileSize diff --git a/venv/Lib/site-packages/flask_wtf/form.py b/venv/Lib/site-packages/flask_wtf/form.py deleted file mode 100644 index c7f52e022c82fe43d6674377e5df040c82d10d79..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/form.py +++ /dev/null @@ -1,127 +0,0 @@ -from flask import current_app -from flask import request -from flask import session -from markupsafe import Markup -from werkzeug.datastructures import CombinedMultiDict -from werkzeug.datastructures import ImmutableMultiDict -from werkzeug.utils import cached_property -from wtforms import Form -from wtforms.meta import DefaultMeta -from wtforms.widgets import HiddenInput - -from .csrf import _FlaskFormCSRF - -try: - from .i18n import translations -except ImportError: - translations = None # babel not installed - - -SUBMIT_METHODS = {"POST", "PUT", "PATCH", "DELETE"} -_Auto = object() - - -class FlaskForm(Form): - """Flask-specific subclass of WTForms :class:`~wtforms.form.Form`. - - If ``formdata`` is not specified, this will use :attr:`flask.request.form` - and :attr:`flask.request.files`. Explicitly pass ``formdata=None`` to - prevent this. - """ - - class Meta(DefaultMeta): - csrf_class = _FlaskFormCSRF - csrf_context = session # not used, provided for custom csrf_class - - @cached_property - def csrf(self): - return current_app.config.get("WTF_CSRF_ENABLED", True) - - @cached_property - def csrf_secret(self): - return current_app.config.get("WTF_CSRF_SECRET_KEY", current_app.secret_key) - - @cached_property - def csrf_field_name(self): - return current_app.config.get("WTF_CSRF_FIELD_NAME", "csrf_token") - - @cached_property - def csrf_time_limit(self): - return current_app.config.get("WTF_CSRF_TIME_LIMIT", 3600) - - def wrap_formdata(self, form, formdata): - if formdata is _Auto: - if _is_submitted(): - if request.files: - return CombinedMultiDict((request.files, request.form)) - elif request.form: - return request.form - elif request.is_json: - return ImmutableMultiDict(request.get_json()) - - return None - - return formdata - - def get_translations(self, form): - if not current_app.config.get("WTF_I18N_ENABLED", True): - return super().get_translations(form) - - return translations - - def __init__(self, formdata=_Auto, **kwargs): - super().__init__(formdata=formdata, **kwargs) - - def is_submitted(self): - """Consider the form submitted if there is an active request and - the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. - """ - - return _is_submitted() - - def validate_on_submit(self, extra_validators=None): - """Call :meth:`validate` only if the form is submitted. - This is a shortcut for ``form.is_submitted() and form.validate()``. - """ - return self.is_submitted() and self.validate(extra_validators=extra_validators) - - def hidden_tag(self, *fields): - """Render the form's hidden fields in one call. - - A field is considered hidden if it uses the - :class:`~wtforms.widgets.HiddenInput` widget. - - If ``fields`` are given, only render the given fields that - are hidden. If a string is passed, render the field with that - name if it exists. - - .. versionchanged:: 0.13 - - No longer wraps inputs in hidden div. - This is valid HTML 5. - - .. versionchanged:: 0.13 - - Skip passed fields that aren't hidden. - Skip passed names that don't exist. - """ - - def hidden_fields(fields): - for f in fields: - if isinstance(f, str): - f = getattr(self, f, None) - - if f is None or not isinstance(f.widget, HiddenInput): - continue - - yield f - - return Markup("\n".join(str(f) for f in hidden_fields(fields or self))) - - -def _is_submitted(): - """Consider the form submitted if there is an active request and - the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. - """ - - return bool(request) and request.method in SUBMIT_METHODS diff --git a/venv/Lib/site-packages/flask_wtf/i18n.py b/venv/Lib/site-packages/flask_wtf/i18n.py deleted file mode 100644 index 1cc0e9c5a6d2fee8d18c4f46a79ed82f93d132a7..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/i18n.py +++ /dev/null @@ -1,47 +0,0 @@ -from babel import support -from flask import current_app -from flask import request -from flask_babel import get_locale -from wtforms.i18n import messages_path - -__all__ = ("Translations", "translations") - - -def _get_translations(): - """Returns the correct gettext translations. - Copy from flask-babel with some modifications. - """ - - if not request: - return None - - # babel should be in extensions for get_locale - if "babel" not in current_app.extensions: - return None - - translations = getattr(request, "wtforms_translations", None) - - if translations is None: - translations = support.Translations.load( - messages_path(), [get_locale()], domain="wtforms" - ) - request.wtforms_translations = translations - - return translations - - -class Translations: - def gettext(self, string): - t = _get_translations() - return string if t is None else t.ugettext(string) - - def ngettext(self, singular, plural, n): - t = _get_translations() - - if t is None: - return singular if n == 1 else plural - - return t.ungettext(singular, plural, n) - - -translations = Translations() diff --git a/venv/Lib/site-packages/flask_wtf/recaptcha/__init__.py b/venv/Lib/site-packages/flask_wtf/recaptcha/__init__.py deleted file mode 100644 index 3100d37e3389219d98787b585357edbe0d9bcc37..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/recaptcha/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .fields import RecaptchaField -from .validators import Recaptcha -from .widgets import RecaptchaWidget diff --git a/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/__init__.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index ad5312065a905cf14e41f922291566616856c8fb..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/fields.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/fields.cpython-311.pyc deleted file mode 100644 index 1eb5b704de329b4e160ea7d0eefa8dd48b16104d..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/fields.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/validators.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/validators.cpython-311.pyc deleted file mode 100644 index b8a1ffd5e1f5b5180ec496e94c61d8d59e9302f6..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/validators.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/widgets.cpython-311.pyc b/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/widgets.cpython-311.pyc deleted file mode 100644 index 6f5bb03a28b7e7b55f3ef3a93a63eda13b3d3754..0000000000000000000000000000000000000000 Binary files a/venv/Lib/site-packages/flask_wtf/recaptcha/__pycache__/widgets.cpython-311.pyc and /dev/null differ diff --git a/venv/Lib/site-packages/flask_wtf/recaptcha/fields.py b/venv/Lib/site-packages/flask_wtf/recaptcha/fields.py deleted file mode 100644 index e91fd092f98c01932a90ffafe68bbf98390ff2ed..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/recaptcha/fields.py +++ /dev/null @@ -1,17 +0,0 @@ -from wtforms.fields import Field - -from . import widgets -from .validators import Recaptcha - -__all__ = ["RecaptchaField"] - - -class RecaptchaField(Field): - widget = widgets.RecaptchaWidget() - - # error message if recaptcha validation fails - recaptcha_error = None - - def __init__(self, label="", validators=None, **kwargs): - validators = validators or [Recaptcha()] - super().__init__(label, validators, **kwargs) diff --git a/venv/Lib/site-packages/flask_wtf/recaptcha/validators.py b/venv/Lib/site-packages/flask_wtf/recaptcha/validators.py deleted file mode 100644 index c5cafb3478cd644a199fb731cf5d2c0c440f986c..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/recaptcha/validators.py +++ /dev/null @@ -1,75 +0,0 @@ -import json -from urllib import request as http -from urllib.parse import urlencode - -from flask import current_app -from flask import request -from wtforms import ValidationError - -RECAPTCHA_VERIFY_SERVER_DEFAULT = "https://www.google.com/recaptcha/api/siteverify" -RECAPTCHA_ERROR_CODES = { - "missing-input-secret": "The secret parameter is missing.", - "invalid-input-secret": "The secret parameter is invalid or malformed.", - "missing-input-response": "The response parameter is missing.", - "invalid-input-response": "The response parameter is invalid or malformed.", -} - - -__all__ = ["Recaptcha"] - - -class Recaptcha: - """Validates a ReCaptcha.""" - - def __init__(self, message=None): - if message is None: - message = RECAPTCHA_ERROR_CODES["missing-input-response"] - self.message = message - - def __call__(self, form, field): - if current_app.testing: - return True - - if request.is_json: - response = request.json.get("g-recaptcha-response", "") - else: - response = request.form.get("g-recaptcha-response", "") - remote_ip = request.remote_addr - - if not response: - raise ValidationError(field.gettext(self.message)) - - if not self._validate_recaptcha(response, remote_ip): - field.recaptcha_error = "incorrect-captcha-sol" - raise ValidationError(field.gettext(self.message)) - - def _validate_recaptcha(self, response, remote_addr): - """Performs the actual validation.""" - try: - private_key = current_app.config["RECAPTCHA_PRIVATE_KEY"] - except KeyError: - raise RuntimeError("No RECAPTCHA_PRIVATE_KEY config set") from None - - verify_server = current_app.config.get("RECAPTCHA_VERIFY_SERVER") - if not verify_server: - verify_server = RECAPTCHA_VERIFY_SERVER_DEFAULT - - data = urlencode( - {"secret": private_key, "remoteip": remote_addr, "response": response} - ) - - http_response = http.urlopen(verify_server, data.encode("utf-8")) - - if http_response.code != 200: - return False - - json_resp = json.loads(http_response.read()) - - if json_resp["success"]: - return True - - for error in json_resp.get("error-codes", []): - if error in RECAPTCHA_ERROR_CODES: - raise ValidationError(RECAPTCHA_ERROR_CODES[error]) - - return False diff --git a/venv/Lib/site-packages/flask_wtf/recaptcha/widgets.py b/venv/Lib/site-packages/flask_wtf/recaptcha/widgets.py deleted file mode 100644 index bfae830bb17f188f2123931847f958590c77e690..0000000000000000000000000000000000000000 --- a/venv/Lib/site-packages/flask_wtf/recaptcha/widgets.py +++ /dev/null @@ -1,43 +0,0 @@ -from urllib.parse import urlencode - -from flask import current_app -from markupsafe import Markup - -RECAPTCHA_SCRIPT_DEFAULT = "https://www.google.com/recaptcha/api.js" -RECAPTCHA_DIV_CLASS_DEFAULT = "g-recaptcha" -RECAPTCHA_TEMPLATE = """ -<script src='%s' async defer></script> -<div class="%s" %s></div> -""" - -__all__ = ["RecaptchaWidget"] - - -class RecaptchaWidget: - def recaptcha_html(self, public_key): - html = current_app.config.get("RECAPTCHA_HTML") - if html: - return Markup(html) - params = current_app.config.get("RECAPTCHA_PARAMETERS") - script = current_app.config.get("RECAPTCHA_SCRIPT") - if not script: - script = RECAPTCHA_SCRIPT_DEFAULT - if params: - script += "?" + urlencode(params) - attrs = current_app.config.get("RECAPTCHA_DATA_ATTRS", {}) - attrs["sitekey"] = public_key - snippet = " ".join(f'data-{k}="{attrs[k]}"' for k in attrs) # noqa: B028, B907 - div_class = current_app.config.get("RECAPTCHA_DIV_CLASS") - if not div_class: - div_class = RECAPTCHA_DIV_CLASS_DEFAULT - return Markup(RECAPTCHA_TEMPLATE % (script, div_class, snippet)) - - def __call__(self, field, error=None, **kwargs): - """Returns the recaptcha input HTML.""" - - try: - public_key = current_app.config["RECAPTCHA_PUBLIC_KEY"] - except KeyError: - raise RuntimeError("RECAPTCHA_PUBLIC_KEY config not set") from None - - return self.recaptcha_html(public_key) diff --git a/wsgi.py b/wsgi.py index 78681907a56f6e8852ba79e6565c4778be08abb6..ab58b4ebdd8b8760b2bbb45528cdda8c27fba07a 100644 --- a/wsgi.py +++ b/wsgi.py @@ -1,8 +1,11 @@ import sys import logging from app import app as application +from markupsafe import Markup + +sys.modules['flask'].Markup = Markup logging.basicConfig(stream=sys.stderr) if __name__ == '__main__': - application.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080))) + application.run(host='0.0.0.0', port=8080)