#![cfg(feature = "git")]
use std::sync::Arc;
use solid_pod_rs::storage::fs::FsBackend;
use solid_pod_rs_server::{build_app, AppState};
const OWNER: &str = "did:nostr:6e1cf6...owner";
fn owner_only_acl(container: &str) -> String {
format!(
r##"{{
"@context": {{"acl": "http://www.w3.org/ns/auth/acl#"}},
"@graph": [{{
"@id": "#owner",
"acl:agent": {{"@id": "{OWNER}"}},
"acl:accessTo": {{"@id": "{container}"}},
"acl:default": {{"@id": "{container}"}},
"acl:mode": [{{"@id": "acl:Read"}}, {{"@id": "acl:Write"}}]
}}]
}}"##
)
}
fn public_read_acl(container: &str) -> String {
format!(
r##"{{
"@context": {{
"acl": "http://www.w3.org/ns/auth/acl#",
"foaf": "http://xmlns.com/foaf/0.1/"
}},
"@graph": [{{
"@id": "#public",
"acl:agentClass": {{"@id": "foaf:Agent"}},
"acl:accessTo": {{"@id": "{container}"}},
"acl:default": {{"@id": "{container}"}},
"acl:mode": {{"@id": "acl:Read"}}
}}]
}}"##
)
}
async fn fs_state_with_pods(pods: &[(&str, String)]) -> (AppState, tempfile::TempDir) {
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path().to_path_buf();
for (pod, acl) in pods {
std::fs::create_dir_all(root.join(pod)).expect("create pod dir");
std::fs::write(root.join(format!("{pod}.acl")), acl).expect("write .acl");
}
let fs = FsBackend::new(root.clone()).await.expect("fs backend");
let mut state = AppState::new(Arc::new(fs));
state.data_root = Some(root);
(state, tmp)
}
#[actix_web::test]
async fn anonymous_clone_of_private_pod_is_denied() {
let (state, _tmp) = fs_state_with_pods(&[("alice", owner_only_acl("/alice/"))]).await;
let app = actix_web::test::init_service(build_app(state)).await;
let req = actix_web::test::TestRequest::get()
.uri("/alice/info/refs?service=git-upload-pack")
.to_request();
let rsp = actix_web::test::call_service(&app, req).await;
let status = rsp.status().as_u16();
assert!(
status == 401 || status == 403,
"anonymous clone of a private pod must be denied by WAC before the CGI \
(expected 401/403, got {status})"
);
}
#[actix_web::test]
async fn anonymous_push_to_private_pod_is_denied() {
let (state, _tmp) = fs_state_with_pods(&[("alice", owner_only_acl("/alice/"))]).await;
let app = actix_web::test::init_service(build_app(state)).await;
let req = actix_web::test::TestRequest::post()
.uri("/alice/git-receive-pack")
.insert_header(("content-type", "application/x-git-receive-pack-request"))
.set_payload(actix_web::web::Bytes::from_static(b""))
.to_request();
let rsp = actix_web::test::call_service(&app, req).await;
let status = rsp.status().as_u16();
assert!(
status == 401 || status == 403,
"anonymous push must be denied by WAC (expected 401/403, got {status})"
);
}
#[actix_web::test]
async fn anonymous_read_of_public_pod_passes_gate() {
let (state, _tmp) = fs_state_with_pods(&[("bob", public_read_acl("/bob/"))]).await;
let app = actix_web::test::init_service(build_app(state)).await;
let req = actix_web::test::TestRequest::get()
.uri("/bob/info/refs?service=git-upload-pack")
.to_request();
let rsp = actix_web::test::call_service(&app, req).await;
let status = rsp.status().as_u16();
assert!(
status != 401 && status != 403,
"anonymous read of a public pod must PASS the WAC gate \
(expected non-401/403, e.g. 404 no-repo; got {status})"
);
}