Flask-WTF выдает ошибку, когда csrf_enabled имеет значение True (установлен SECRET_KEY) - PullRequest
0 голосов
/ 07 ноября 2019

Я столкнулся с проблемой, связанной с защитой Flask-WTF от csrf.

Когда создается экземпляр формы следующим образом:

uform = UserForm(csrf_enabled=False)

все работает, как ожидается, и форма работает правильноотображается. Однако:

uform = UserForm()

приводит к ошибке TypeError:

Traceback (most recent call last):
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 1952, in full_dispatch_request
    return self.finalize_request(rv)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 1969, in finalize_request
    response = self.process_response(response)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/app.py", line 2268, in process_response
    self.session_interface.save_session(self, ctx.session, response)
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/sessions.py", line 387, in save_session
    samesite=samesite,
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/werkzeug/wrappers/base_response.py", line 481, in set_cookie
    samesite=samesite,
  File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/werkzeug/http.py", line 1163, in dump_cookie
    buf = [key + b"=" + _cookie_quote(value)]
TypeError: unsupported operand type(s) for +: 'NoneType' and 'bytes'

SECRET_KEY установлен (как и WTF_CSRF_SECRET_KEY, просто чтобы убедиться, что Flask-WTF этого не ожидал, дажехотя согласно документам это необязательно).

Вот остальная часть (соответствующего) кода:

admin / rout.py

from flask import current_app as app
from .. import db
from ..models import User
from .forms import UserForm

# Set up a Blueprint
admin_bp = Blueprint('admin_bp', __name__, template_folder='templates', static_folder='static')

@admin_bp.route("users/add/", methods=["GET", "POST"])
def add_user():
    #Add user to the DB
    add_user = True

    #uform = UserForm(csrf_enabled=True)
    uform = UserForm()

    if uform.validate_on_submit():
        user = User(username=uform.username.data, email=uform.email.data, admin=uform.admin.data, address=uform.address.data, delivery=uform.delivery.data)
        user.pwhash(uform.password.data)

        try:
            db.session.add(user)
            db.session.commit()
            flash("Benutzer '", user.username, "' erfolgreich hinzugef  gt.")
        except:
            flash("Fehler: Benutzer konnte nicht hinzugef  gt werden. Existiert Benutzer bereits?")

        return redirect(url_for("admin_bp.users"))

    return render_template("/user.html", action="Hinzuf  gen", add_user=add_user, uform=uform, title="Benutzer hinzuf  gen")

admin / forms.py

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField, BooleanField, SelectField
from wtforms.validators import DataRequired, Email

class UserForm(FlaskForm):
    username = StringField("Username", validators=[DataRequired()])
    email = StringField("Email", validators=[DataRequired(), Email()])
    password = StringField("Neues Passwort")
    address = StringField("Adresse")
    admin = BooleanField("Admin?")
    delivery = SelectField("Delivery", choices=[("1", "test1"), ("2", "test2")])
    submit = SubmitField("OK")

__ init__.py:

from flask_sqlalchemy import SQLAlchemy
from flask_static_compress import FlaskStaticCompress
from flask_bootstrap import Bootstrap
from flask_wtf.csrf import CSRFProtect

# Globally accessible libraries
db = SQLAlchemy()
bs = Bootstrap()
csrf = CSRFProtect()

def create_app():
    app = Flask(__name__, instance_relative_config=False, static_folder="static", template_folder="templates")
    from config import Config
    app.config.from_object('config.DevConfig')
    db.init_app(app)
    bs.init_app(app)
    csrf.init_app(app)

    with app.app_context():

        from .models import Image, Gallery, ImageGalleryMap, Delivery, ImageDeliveryMap, Blogpost, User, Log
        db.create_all()

        compress = FlaskStaticCompress(app)

        # Register Blueprints
        from website.admin.routes import admin_bp
        from website.delivery.routes import delivery_bp
        from website.landing.routes import landing_bp
        from website.public.routes import public_bp

        app.register_blueprint(admin_bp, url_prefix='/admin')
        app.register_blueprint(delivery_bp, url_prefix='/delivery')
        app.register_blueprint(landing_bp, url_prefix='/landing')
        app.register_blueprint(public_bp, url_prefix='/')

        return app

У кого-нибудь есть идеи, как это исправить? По (очень очевидным) причинам я не очень заинтересован в установке WTF_CSRF_ENABLED = False ...

1 Ответ

0 голосов
/ 09 ноября 2019

Единственный способ, которым я могу воспроизвести Traceback, который вы дали в вашем примере, - это если app.secret_key допустимо, но app.session_cookie_name установлено на None.

. Я использовал для тестирования. Запуск этого скрипта и посещение http://127.0.0.1:5000 поднимает TypeError: unsupported operand type(s) for +: 'NoneType' and 'bytes', что является точным исключением, которое вы опубликовали выше. Если секретный ключ не установлен, мы получаем другую ошибку, когда пытаемся создать экземпляр формы, перед тем как фляжка попытается обработать запрос, который она вызывает KeyError: 'A secret key is required to use CSRF.'. Вот сценарий:

from flask import Flask, render_template_string
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField


app = Flask(__name__)
# comment out below and a KeyError is raised, not a TypeError.
# So the fact that the code raises the TypeError from trying to generate
# a cookie means that the secret key is set, the form is instantiating and
# flask is trying to serve the request. So we can rule out this being a
# problem with secret key not set.
app.secret_key = "lskdjflksdj"
# comment out below and the app runs
app.session_cookie_name = None


class MyForm(FlaskForm):
    a_str = StringField()
    submit = SubmitField()


template = """
<form action="" method="POST">
{{form.a_str()}}
{{form.submit()}}
</form>
"""


@app.route("/", methods=["GET", "POST"])
def route():
    # setting csrf_enabled=False makes the problem go away.
    form = MyForm(csrf_enabled=True)
    return render_template_string(template, form=form)


if __name__ == "__main__":
    app.run(debug=True)

Так почему установка csrf_enabled=False в форме останавливает ошибку? Токен, который генерирует колба при возникновении ошибки, является токеном csrf. Когда мы отключаем проверку csrf в форме, этот токен больше не нужно генерировать, и мы не сталкиваемся с ошибкой.

Тот факт, что переключение защиты csrf в форме имеет непосредственное отношение к возникновению ошибкиили нет, выглядит ли это как проблема с конфигурацией секретного ключа, но реальный вопрос заключается в том, почему значение параметра key передается в dump_cookie() NoneType?

Если вы следуетеОтследите, вы увидите, что последний вызов внутри библиотеки фляги находится здесь: File "/home/charlotte/hochzeit/venv/lib/python3.6/site-packages/flask/sessions.py", line 387, in save_session. Вот источник :

        response.set_cookie(
            name,
            val,
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite,
        )

Вы можете убедиться, , что первый аргумент, переданный set_cookie выше, передается параметру key вwerkzeug.wrappers.base_response.set_cookie, который сам передается параметру key в werkzeug.http.dump_cookie (в сообщении об ошибке None).

Значение name во фрагменте save_session выше определено ранее в функции как name = self.get_cookie_name(app), а тело метода get_cookie_name просто return app.session_cookie_name.

Значение по умолчанию для app.session_cookie_name это "session", но каким-то образом это переопределяется с None в вашей конфигурации.

...