【问题标题】:Flask disable CSRF in unittestFlask 在单元测试中禁用 CSRF
【发布时间】:2016-12-02 02:51:54
【问题描述】:

在我的项目 __init__.py 我有这个:

app = Flask(__name__)
app.config.from_object('config')
CsrfProtect(app)
db = SQLAlchemy(app)

我的开发配置文件如下所示:

import os
basedir = os.path.abspath(os.path.dirname(__file__))

DEBUG = True
WTF_CSRF_ENABLED = True
SECRET_KEY = 'supersecretkey'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'project.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False

在我的单元测试设置中,我有这个:

from project import app, db

class ExampleTest(unittest.TestCase):
   def setUp(self):
        app.config['TESTING'] = True
        app.config['WTF_CSRF_ENABLED'] = False
        app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
        self.app = app.test_client()
        db.create_all()

理论上,在此处将 WTF_CSRF_ENABLED 设置为 False 应该可以防止 CSRF 用于单元测试,但是如果我在单元测试时执行 POST,我仍然会收到 CSRF 错误。我认为这是因为我已经调用了 CsrfProtect(app) 而 WTF_CSRF_ENABLED 为 True (当我导入应用程序时,它被调用)。如果我在配置文件中设置 WTF_CSRF_ENABLED = False,它会按预期工作。

在启用 CSRF 后,我是否可以禁用它?还是我在这里找错树了?

【问题讨论】:

  • 我认为一旦您导入应用程序,该代码就会运行,因此它将使用您的默认配置运行,因此之后当您进行测试时,您是否覆盖配置并不重要
  • 是的,这也是我的想法。我希望事后有某种方法可以禁用它,也许不是通过 app.config,而是像 StopCsrfProtect(app) 之类的东西。但这可能是一厢情愿的想法。
  • 我想最简单的解决方法是在测试时禁用 CSRF 来运行它。
  • 我可以这样做,但我担心它可能会弄乱任何集成测试。我要么需要记住在运行单元测试时来回更改配置文件,要么在开发时冒着看不到真正的 csrf 错误的风险(忘记在 jinja 模板中放置 hidden_​​tag 或其他东西)。嗯……

标签: flask flask-wtforms


【解决方案1】:

您可以使用配置变量 WTF_CSRF_ENABLED 禁用它,

例如

class TestConfig(Config):
    TESTING = True
    WTF_CSRF_ENABLED = False
    ...

app.config['WTF_CSRF_ENABLED'] = False

另见flask-WTF documentation

【讨论】:

    【解决方案2】:

    如果可以的话,保留csrf_token 并不难。我能够使用一些正则表达式并使用Flask docs 中关于测试的登录功能成功登录到使用csrf_token 的应用程序。

    def login(self, username, password):
        rv = self.client.get('/login')
        m = re.search(b'(<input id="csrf_token" name="csrf_token" type="hidden" value=")([-A-Za-z.0-9]+)', rv.data)
    
        return self.client.post('/login', data=dict(
            userName=username,
            password=password,
            csrf_token=m.group(2).decode("utf-8")
        ), follow_redirects=True)
    

    所以我在这里所做的是使csrf_token 成为第二个捕获组的一部分。这可以很容易地用于在整个应用程序中查找令牌。

    【讨论】:

    • 这个正则表达式对我来说失败了很多。更新并持续工作:m = re.search(b'&lt;input id="csrf_token" name="csrf_token" type="hidden" value="(.*)"&gt;', rv.data) 并解码m.group(1).decode("utf-8"),因为没有理由捕获第一组。您缺少下划线,但没有理由定义有效字符,因为最终的 " 无论如何都会正确终止捕获组。我宁愿只关闭 csrf,但这似乎导致模板现在对我来说失败了。我完全不明白为什么。
    【解决方案3】:

    查看 csrf_protect 的代码,它会在每次请求进入时检查 app.config['WTF_CSRF_METHODS'] 以查看此请求类型是否应受 CSRF 保护。默认情况下,受保护的方法是:

    app.config.setdefault('WTF_CSRF_METHODS', ['POST', 'PUT', 'PATCH'])
    

    因为它实际上每次都会检查 app.config,所以只需在我的单元测试设置中将其更改为空列表即可解决问题:

    from project import app, db
    
    class ExampleTest(unittest.TestCase):
       def setUp(self):
            app.config['TESTING'] = True
            app.config['WTF_CSRF_METHODS'] = []  # This is the magic
            app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
            self.app = app.test_client()
            db.create_all()
    

    另外,它确实使用 app.before_request() 注册了 csrf 保护,所以我认为可以通过修改 before request functions 来取消注册它。但我认为走这条路更有可能在未来的更新中看到问题。

    【讨论】:

    • 创建一个自定义类扩展单元测试并覆盖此配置并在其他测试类中使用它
    猜你喜欢
    • 1970-01-01
    • 2015-05-13
    • 2015-09-06
    • 2013-11-26
    • 1970-01-01
    • 1970-01-01
    • 2015-11-15
    • 2017-01-30
    • 1970-01-01
    相关资源
    最近更新 更多