会重定向的守卫
机器翻译
本页由英文原文机器翻译而来,尚未经过母语审校,欢迎在 GitHub 上提出修改。内容如有出入,以英文原文为准。
任务: 把未登录的访问者带着一条消息送到登录页面,并确保他们也无法向页面的动作提交数据。
yaml
# Recipe: a guard -- a top-level q:if with q:redirect -- protects the page and
# its actions.
paths:
components: ./components
migrations: ./migrations
datasources:
db:
driver: sqlite
database: ./data/notes.db1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
sql
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author TEXT NOT NULL,
body TEXT NOT NULL
);1
2
3
4
5
2
3
4
5
位于页面顶部、分支中带有 q:redirect 的 q:if 就是一个守卫。它在页面之前、 也在页面的每个动作之前运行,所以直接发送到 add 的提交也会被拦下:
xml
<q:component name="Notes">
<!-- The guard: it runs before the page AND before each of its actions. -->
<q:if condition="not session.authenticated">
<q:redirect url="/login" flash="Sign in to write notes." />
</q:if>
<q:action name="add" method="POST">
<q:param name="body" required="true" minlength="2" />
<q:query name="added" datasource="db">
INSERT INTO notes (author, body) VALUES (:author, :body)
<q:param name="author" value="{session.userName}" type="string" />
<q:param name="body" value="{body}" type="string" />
</q:query>
<q:redirect url="/" flash="Saved." />
</q:action>
<q:query name="notes" datasource="db">
SELECT author, body FROM notes ORDER BY id DESC
</q:query>
<h1>Notes</h1>
<q:if condition="flash"><p>{flash}</p></q:if>
<form method="POST"><input name="body" /><button>Add</button></form>
<q:loop query="notes"><p>{notes.author}: {notes.body}</p></q:loop>
</q:component>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
xml
<q:component name="Login">
<q:if condition="flash"><p>{flash}</p></q:if>
<h1>Sign in</h1>
</q:component>1
2
3
4
2
3
4
第二个测试在没有会话的情况下向动作提交,并检查没有写入任何行:
xml
<q:test name="the guard sends a visitor to sign in" page="/">
<test:visit />
<test:expect status="302" redirect="/login" />
<test:expect text="Sign in to write notes." />
</q:test>
<q:test name="the guard also stops the action: nothing is written" page="/">
<test:submit action="add" body="Sneaky note" />
<test:expect status="302" redirect="/login" />
<test:expect table="notes" count="0" />
</q:test>
<q:test name="a signed-in user writes a note" page="/">
<test:as user="ana" role="member" />
<test:submit action="add" body="Buy coffee" />
<test:expect redirect="/" flash="Saved." />
<test:expect table="notes" count="1" where="author = 'ana' AND body = 'Buy coffee'" />
<test:expect text="ana: Buy coffee" />
</q:test>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
text
tests/guard.test.q
PASS the guard sends a visitor to sign in
PASS the guard also stops the action: nothing is written
PASS a signed-in user writes a note
3 passed, 0 failed1
2
3
4
5
2
3
4
5
守卫可以检查会话中的任何内容。如果只是要求已登录的用户或某个角色,require_auth 和 require_role 用一个属性就能表达(只对一个角色开放的页面)。