| 1 | |
| 2 | |
| 3 | |
| 4 | use std::sync::Arc; |
| 5 | |
| 6 | use forge_core::{Role, Store, Visibility}; |
| 7 | use topcoat::router::request::Request; |
| 8 | use topcoat::router::{Body, Router, to_bytes}; |
| 9 | |
| 10 | use crate::auth::SESSION_COOKIE; |
| 11 | |
| 12 | |
| 13 | |
| 14 | const SECURITY_TAB: &str = "href=\"/owner/proj/security\""; |
| 15 | const CSRF: &str = "security-tab-test-csrf"; |
| 16 | |
| 17 | struct Site { |
| 18 | router: Router, |
| 19 | store: Store, |
| 20 | _dir: tempfile::TempDir, |
| 21 | } |
| 22 | |
| 23 | async fn site(advisories: bool) -> Site { |
| 24 | let dir = tempfile::tempdir().expect("tempdir"); |
| 25 | let mut state = crate::test_support::test_state(dir.path()).await; |
| 26 | let mut config = crate::test_support::test_config(dir.path()); |
| 27 | config.advisories.enabled = advisories; |
| 28 | state.config = Arc::new(config); |
| 29 | let store = state.store.clone(); |
| 30 | let router = crate::test_support::app_router(state); |
| 31 | Site { |
| 32 | router, |
| 33 | store, |
| 34 | _dir: dir, |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | |
| 39 | async fn sign_in(store: &Store, user_id: &str, token: &str) -> String { |
| 40 | crate::test_support::create_session(store, user_id, token, CSRF).await; |
| 41 | format!("__Host-{SESSION_COOKIE}={token}") |
| 42 | } |
| 43 | |
| 44 | async fn get(site: &Site, uri: &str, cookie: Option<&str>) -> (u16, String) { |
| 45 | let mut builder = Request::builder().method("GET").uri(uri); |
| 46 | if let Some(cookie) = cookie { |
| 47 | builder = builder.header("cookie", cookie); |
| 48 | } |
| 49 | let res = site |
| 50 | .router |
| 51 | .handle(builder.body(Body::from(String::new())).expect("request")) |
| 52 | .await; |
| 53 | let status = res.status().as_u16(); |
| 54 | let bytes = to_bytes(res.into_body(), usize::MAX).await.expect("body"); |
| 55 | (status, String::from_utf8_lossy(&bytes).into_owned()) |
| 56 | } |
| 57 | |
| 58 | |
| 59 | async fn fixture(site: &Site) -> (String, String) { |
| 60 | let owner = site |
| 61 | .store |
| 62 | .create_user("owner", None, "unused", false) |
| 63 | .await |
| 64 | .unwrap(); |
| 65 | site.store |
| 66 | .create_repo(&owner.id, "proj", "", Visibility::Public, "main") |
| 67 | .await |
| 68 | .unwrap(); |
| 69 | let owner_cookie = sign_in(&site.store, &owner.id, "owner-token").await; |
| 70 | let reader = site |
| 71 | .store |
| 72 | .create_user("reader", None, "unused", false) |
| 73 | .await |
| 74 | .unwrap(); |
| 75 | let reader_cookie = sign_in(&site.store, &reader.id, "reader-token").await; |
| 76 | (owner_cookie, reader_cookie) |
| 77 | } |
| 78 | |
| 79 | #[tokio::test] |
| 80 | async fn security_tab_shows_to_the_owner_when_advisories_are_on() { |
| 81 | let site = site(true).await; |
| 82 | let (owner_cookie, _) = fixture(&site).await; |
| 83 | let (status, body) = get(&site, "/owner/proj/issues", Some(&owner_cookie)).await; |
| 84 | assert_eq!(status, 200); |
| 85 | assert!( |
| 86 | body.contains(SECURITY_TAB), |
| 87 | "owner should see the Security tab" |
| 88 | ); |
| 89 | assert!(body.contains("/owner/proj/security")); |
| 90 | } |
| 91 | |
| 92 | #[tokio::test] |
| 93 | async fn security_tab_shows_to_write_collaborators() { |
| 94 | let site = site(true).await; |
| 95 | fixture(&site).await; |
| 96 | let writer = site |
| 97 | .store |
| 98 | .create_user("writer", None, "unused", false) |
| 99 | .await |
| 100 | .unwrap(); |
| 101 | let repo = site |
| 102 | .store |
| 103 | .repo_by_owner_and_name("owner", "proj") |
| 104 | .await |
| 105 | .unwrap() |
| 106 | .expect("repo"); |
| 107 | site.store |
| 108 | .set_collaborator(&repo.id, &writer.id, Role::Write) |
| 109 | .await |
| 110 | .unwrap(); |
| 111 | let writer_cookie = sign_in(&site.store, &writer.id, "writer-token").await; |
| 112 | let (status, body) = get(&site, "/owner/proj/issues", Some(&writer_cookie)).await; |
| 113 | assert_eq!(status, 200); |
| 114 | assert!( |
| 115 | body.contains(SECURITY_TAB), |
| 116 | "a Write collaborator should see the tab" |
| 117 | ); |
| 118 | } |
| 119 | |
| 120 | #[tokio::test] |
| 121 | async fn security_tab_is_hidden_from_readers() { |
| 122 | let site = site(true).await; |
| 123 | let (_, reader_cookie) = fixture(&site).await; |
| 124 | let (status, body) = get(&site, "/owner/proj/issues", Some(&reader_cookie)).await; |
| 125 | assert_eq!(status, 200); |
| 126 | assert!( |
| 127 | !body.contains(SECURITY_TAB), |
| 128 | "a reader must not see the tab" |
| 129 | ); |
| 130 | } |
| 131 | |
| 132 | #[tokio::test] |
| 133 | async fn security_tab_is_hidden_when_advisories_are_off() { |
| 134 | let site = site(false).await; |
| 135 | let (owner_cookie, _) = fixture(&site).await; |
| 136 | let (status, body) = get(&site, "/owner/proj/issues", Some(&owner_cookie)).await; |
| 137 | assert_eq!(status, 200); |
| 138 | assert!( |
| 139 | !body.contains(SECURITY_TAB), |
| 140 | "no tab while advisories are off" |
| 141 | ); |
| 142 | } |