forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
7.0 KiB
1//! Router-level tests for the GPG keys settings page: login, CSRF, deleting only your own keys
2//! and the unavailable notice.
3
4use forge_core::Store;
5use topcoat::router::request::Request;
6use topcoat::router::{Body, Router, to_bytes};
7
8use crate::auth::SESSION_COOKIE;
9
10const CSRF: &str = "gpg-test-csrf";
11const TOKEN: &str = "gpg-test-token";
12const FINGERPRINT: &str = "ABCD1234ABCD1234ABCD1234ABCD1234ABCD1234";
13
14struct Site {
15 router: Router,
16 store: Store,
17 _dir: tempfile::TempDir,
18}
19
20struct Reply {
21 status: u16,
22 location: Option<String>,
23 body: String,
24}
25
26/// A fresh site; the GPG availability is reported alongside it so tests can check the notice.
27async fn site() -> (Site, bool) {
28 let dir = tempfile::tempdir().expect("tempdir");
29 let state = crate::test_support::test_state(dir.path()).await;
30 let available = crate::signatures::gpg_available(&state).await;
31 let store = state.store.clone();
32 let router = crate::test_support::app_router(state);
33 (
34 Site {
35 router,
36 store,
37 _dir: dir,
38 },
39 available,
40 )
41}
42
43async fn sign_in(store: &Store, user_id: &str) -> String {
44 crate::test_support::create_session(store, user_id, TOKEN, CSRF).await;
45 format!("__Host-{SESSION_COOKIE}={TOKEN}")
46}
47
48async fn call(
49 site: &Site,
50 method: &str,
51 uri: &str,
52 cookie: Option<&str>,
53 fields: Option<&[(&str, &str)]>,
54) -> Reply {
55 let mut builder = Request::builder().method(method).uri(uri);
56 if let Some(cookie) = cookie {
57 builder = builder.header("cookie", cookie);
58 }
59 let body = match fields {
60 Some(fields) => {
61 builder = builder.header("content-type", "application/x-www-form-urlencoded");
62 Body::from(
63 form_urlencoded::Serializer::new(String::new())
64 .extend_pairs(fields)
65 .finish(),
66 )
67 }
68 None => Body::empty(),
69 };
70 let res = site
71 .router
72 .handle(builder.body(body).expect("request"))
73 .await;
74 let status = res.status().as_u16();
75 let location = res
76 .headers()
77 .get("location")
78 .and_then(|v| v.to_str().ok())
79 .map(str::to_owned);
80 let bytes = to_bytes(res.into_body(), usize::MAX).await.expect("body");
81 Reply {
82 status,
83 location,
84 body: String::from_utf8_lossy(&bytes).into_owned(),
85 }
86}
87
88#[tokio::test]
89async fn gpg_page_requires_login() {
90 let (site, _) = site().await;
91 let reply = call(&site, "GET", "/settings/gpg", None, None).await;
92 assert_eq!(reply.status, 303);
93 assert!(
94 reply
95 .location
96 .as_deref()
97 .is_some_and(|l| l.starts_with("/login")),
98 "redirects to login, got {:?}",
99 reply.location
100 );
101}
102
103#[tokio::test]
104async fn adding_a_key_requires_login() {
105 let (site, _) = site().await;
106 let fields = [("csrf", CSRF), ("armored", "junk")];
107 let reply = call(&site, "POST", "/settings/gpg", None, Some(&fields)).await;
108 assert_eq!(reply.status, 303);
109}
110
111#[tokio::test]
112async fn adding_a_key_rejects_a_bad_csrf_token() {
113 let (site, _) = site().await;
114 let alice = site
115 .store
116 .create_user("alice", None, "h", false)
117 .await
118 .unwrap();
119 let cookie = sign_in(&site.store, &alice.id).await;
120 let fields = [("csrf", "wrong"), ("armored", "junk")];
121 let reply = call(&site, "POST", "/settings/gpg", Some(&cookie), Some(&fields)).await;
122 assert_eq!(reply.status, 403);
123}
124
125#[tokio::test]
126async fn deleting_a_key_requires_login_and_csrf() {
127 let (site, _) = site().await;
128 let alice = site
129 .store
130 .create_user("alice", None, "h", false)
131 .await
132 .unwrap();
133 let key = site
134 .store
135 .add_gpg_key(
136 &alice.id,
137 FINGERPRINT,
138 "armored",
139 &["alice@example.com".into()],
140 )
141 .await
142 .unwrap();
143 let fields = [("csrf", CSRF), ("id", key.id.as_str())];
144 let anon = call(&site, "POST", "/settings/gpg/delete", None, Some(&fields)).await;
145 assert_eq!(anon.status, 303);
146
147 let cookie = sign_in(&site.store, &alice.id).await;
148 let bad = [("csrf", "wrong"), ("id", key.id.as_str())];
149 let reply = call(
150 &site,
151 "POST",
152 "/settings/gpg/delete",
153 Some(&cookie),
154 Some(&bad),
155 )
156 .await;
157 assert_eq!(reply.status, 403);
158 assert_eq!(site.store.list_gpg_keys(&alice.id).await.unwrap().len(), 1);
159}
160
161#[tokio::test]
162async fn cannot_delete_another_users_key() {
163 let (site, _) = site().await;
164 let alice = site
165 .store
166 .create_user("alice", None, "h", false)
167 .await
168 .unwrap();
169 let bob = site
170 .store
171 .create_user("bob", None, "h", false)
172 .await
173 .unwrap();
174 let key = site
175 .store
176 .add_gpg_key(&bob.id, FINGERPRINT, "armored", &["bob@example.com".into()])
177 .await
178 .unwrap();
179 let cookie = sign_in(&site.store, &alice.id).await;
180 let fields = [("csrf", CSRF), ("id", key.id.as_str())];
181 let reply = call(
182 &site,
183 "POST",
184 "/settings/gpg/delete",
185 Some(&cookie),
186 Some(&fields),
187 )
188 .await;
189 assert_eq!(reply.status, 404);
190 assert_eq!(site.store.list_gpg_keys(&bob.id).await.unwrap().len(), 1);
191}
192
193#[tokio::test]
194async fn owner_can_delete_their_own_key() {
195 let (site, _) = site().await;
196 let alice = site
197 .store
198 .create_user("alice", None, "h", false)
199 .await
200 .unwrap();
201 let key = site
202 .store
203 .add_gpg_key(
204 &alice.id,
205 FINGERPRINT,
206 "armored",
207 &["alice@example.com".into()],
208 )
209 .await
210 .unwrap();
211 let cookie = sign_in(&site.store, &alice.id).await;
212 let fields = [("csrf", CSRF), ("id", key.id.as_str())];
213 let reply = call(
214 &site,
215 "POST",
216 "/settings/gpg/delete",
217 Some(&cookie),
218 Some(&fields),
219 )
220 .await;
221 assert_eq!(reply.status, 303);
222 assert!(
223 site.store
224 .list_gpg_keys(&alice.id)
225 .await
226 .unwrap()
227 .is_empty()
228 );
229}
230
231#[tokio::test]
232async fn page_lists_keys_and_shows_the_notice_only_when_gpg_is_missing() {
233 let (site, available) = site().await;
234 let alice = site
235 .store
236 .create_user("alice", None, "h", false)
237 .await
238 .unwrap();
239 site.store
240 .add_gpg_key(
241 &alice.id,
242 FINGERPRINT,
243 "armored",
244 &["alice@example.com".into()],
245 )
246 .await
247 .unwrap();
248 let cookie = sign_in(&site.store, &alice.id).await;
249 let reply = call(&site, "GET", "/settings/gpg", Some(&cookie), None).await;
250 assert_eq!(reply.status, 200);
251 assert!(reply.body.contains(FINGERPRINT));
252 assert!(reply.body.contains("alice@example.com"));
253 let notice = "GPG verification is unavailable on this server";
254 assert_eq!(reply.body.contains(notice), !available);
255}