为什么这些自定义Flask会话界面的测试失败?

Jul*_*ian 8 python session customization flask

我正在Flask中编写混合单页web/PhoneGap应用程序.由于PhoneGap应用程序中的cookie基本上不可用,我实现了一个完全避免cookie 的自定义会话接口.它将会话数据存储在应用程序数据库中,并在HTTP请求和响应主体中显式传递会话ID.

我创建了一个带有简化测试用例的GitHub存储库.它本身仍然是一个相当大的项目,但自述文件应该可以帮助您快速找到自己的方式.repo包括七个测试,当使用Flask的默认基于cookie的会话接口时,所有测试都成功,并且所有测试都失败了我的自定义会话接口.主要问题似乎是数据有时不会保留在会话对象上,但这很神秘,因为会话对象继承自Python的内置dict,它不应该自发地忘记数据.此外,与Flask的示例Redis会话片段相比,会话界面简单明了,似乎没有任何明显的错误.

更令人沮丧的是,自定义会话界面似乎在实际应用程序中正常工作.只有单元测试失败.但是,由于这个原因,假设会话接口在所有情况下都能正常工作是不安全的.

将非常感谢帮助.

编辑: Gist不接受缩减的测试用例,因为它包含目录.我现在将它移动到一个完整的GitHub存储库.完成后我会再次更新这篇文章.

新编辑:将简化的测试用例移动到适当的GitHub存储库.自述文件仍然提到"这个要点",对不起.

Jer*_*len 7

您的问题主要归结为在测试请求中提供会话令牌.如果您未提供令牌,则会话为空.

我假设您的实际应用程序正确发送会话令牌,因此似乎工作.

修复测试用例以正确传递并不需要太多时间.

每个请求都尝试基于post param加载会话

在您的会话实现中:

def open_session(self, app, request):
    s = Session()
    if 't' in request.form:
        ....

    return s
Run Code Online (Sandbox Code Playgroud)

这意味着每个未POST(或PUT)未t发送的请求都将具有空白会话.

而基于cookie的实现将始终具有会话令牌,并且能够加载先前请求的会话.

以下是您的一个示例测试:

def test_authorize_captcha_expired(self):
    with self.client as c:
        with c.session_transaction() as s:
            s['captcha-answer'] = u'one two three'.split()
            s['captcha-expires'] = datetime.today() - timedelta(minutes=1)
        self.assertEqual(c.post('/test', data={
            'ca': 'one two three',
        }).status_code, 400)
Run Code Online (Sandbox Code Playgroud)

您尚未t为帖子提供值/test.因此它得到一个没有captcha-expires密钥的空白会话,并且KeyError被引发.

您的会话需要一个"令牌"键才能保存

在您的会话实现中:

def save_session(self, app, session, response):
    if session.modified and 'token' in session:
        ...
        # save session to database
        ...
Run Code Online (Sandbox Code Playgroud)

因此当你有:

with c.session_transaction() as s:
    s['captcha-answer'] = u'one two three'.split()
    s['captcha-expires'] = datetime.today() - timedelta(minutes=1)
Run Code Online (Sandbox Code Playgroud)

实际上没有会话被写入数据库.对于任何后续使用请求.需要注意的是它真的没有需要被写入到数据库中,因为open_session将尝试在每次请求从数据库装载的东西.

要解决大多数情况,您需要在创建会话时提供"令牌",并为使用它的任何请求提供带有该令牌的"t".

因此,我上面使用的样本测试最终会像:

def test_authorize_captcha_expired(self):
    with self.client as c:
        token = generate_key(SystemRandom())
        with c.session_transaction() as s:
            s['token'] = token
            s['captcha-answer'] = u'one two three'.split()
            s['captcha-expires'] = datetime.today() - timedelta(minutes=1)
        self.assertEqual(c.post('/test', data={
            'ca': 'one two three',
            't': token
        }).status_code, 400)
Run Code Online (Sandbox Code Playgroud)

使用json响应时更改令牌

...但是当您发出后续请求时,您没有使用新令牌

def test_reply_to_reflection_passthrough(self):
    with self.client as c:
        token = 'abcdef'

        ...

        response2 = c.post('/reflection/1/reply', data={
            'p': 'test4',
            'r': 'testmessage',
            't': token,
        }, ...
Run Code Online (Sandbox Code Playgroud)

通过此处,帖子/reflection/1/reply生成了一个新令牌并保存了它,因此关键键last-reply不在由标识的会话中abcdef.如果这是基于cookie的会话last-reply,则可用于下一个请求.

所以要修复此测试...使用新令牌

def test_reply_to_reflection_passthrough(self):
    with self.client as c:

        ...

        response2 = c.post('/reflection/1/reply', data={

        ...

        token = session['token']
        with c.session_transaction(method="POST", data={'t':token}) as s:
            s['token'] = token
            s['last-request'] = datetime.now() - timedelta(milliseconds=1001)

        response3 = c.post('/reflection/1/reply', data={

        ...
Run Code Online (Sandbox Code Playgroud)

重定向将丢失会话令牌

在测试中test_bump:

def test_bump(self):
    response = self.client.post(
        '/admin/tip/action/',
        data = {'action': 'Bump', 'rowid': '1',},
        follow_redirects=True )
    self.assertIn(' tips have been bumped.', response.data)
Run Code Online (Sandbox Code Playgroud)

帖子/admin/tip/action返回重定向.

在这里,您要检查是否存在Flash消息.并且flash消息存储在会话中.

使用基于cookie的会话,会话ID再次与随后的重定向请求一起发送.

由于您的会话ID被指定为post值,因此不会再次发送,会话和flash消息将丢失.

解决此问题的方法不是遵循重定向,而是检查会话以获取由flasks flash方法设置的数据.

def test_bump(self):
    with self.client as c:
        token = generate_key(SystemRandom())
        with c.session_transaction() as s:
            s['token'] = token
        c.post('/admin/tip/action/',
               data={'action': 'Bump', 'rowid': '1', 't': token})

        with c.session_transaction(method="POST", data={'t': token}) as s:
            self.assertIn(' tips have been bumped.', s['_flashes'][0][1])
Run Code Online (Sandbox Code Playgroud)

就这样

我已经发送了一个带有更改的pull请求,如上所述,您会发现测试现在都通过了默认的flask会话和会话实现.