diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 03:38:21 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 03:38:21 +0000 |
| commit | 3f74ababf338d65ac5e29e7eb5541ce416b7fe75 (patch) | |
| tree | 3e06696a1833db776a7d1908dd2812dcbe86f6a7 /grasp-audit/src/specs | |
| parent | c15215d704117d1035806e3b5f71afc19f5516a8 (diff) | |
add git http advertisment allow-reachable-sha1-in-want and allow-tip-sha1-in-want
Diffstat (limited to 'grasp-audit/src/specs')
| -rw-r--r-- | grasp-audit/src/specs/grasp01/git_clone.rs | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/grasp-audit/src/specs/grasp01/git_clone.rs b/grasp-audit/src/specs/grasp01/git_clone.rs index 8c91c04..4666a40 100644 --- a/grasp-audit/src/specs/grasp01/git_clone.rs +++ b/grasp-audit/src/specs/grasp01/git_clone.rs | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | //! - Basic clone operation via HTTP | 7 | //! - Basic clone operation via HTTP |
| 8 | //! - Cloned repository structure validation | 8 | //! - Cloned repository structure validation |
| 9 | //! - Clone URL format verification | 9 | //! - Clone URL format verification |
| 10 | //! - SHA1 capability advertisement verification | ||
| 10 | //! | 11 | //! |
| 11 | //! ## Running Tests | 12 | //! ## Running Tests |
| 12 | //! | 13 | //! |
| @@ -34,6 +35,7 @@ impl GitCloneTests { | |||
| 34 | 35 | ||
| 35 | results.add(Self::test_basic_git_clone(client, git_data_dir, relay_domain).await); | 36 | results.add(Self::test_basic_git_clone(client, git_data_dir, relay_domain).await); |
| 36 | results.add(Self::test_clone_url_format(client, git_data_dir, relay_domain).await); | 37 | results.add(Self::test_clone_url_format(client, git_data_dir, relay_domain).await); |
| 38 | results.add(Self::test_sha1_capabilities_advertised(client, git_data_dir, relay_domain).await); | ||
| 37 | 39 | ||
| 38 | results | 40 | results |
| 39 | } | 41 | } |
| @@ -277,6 +279,158 @@ impl GitCloneTests { | |||
| 277 | ) | 279 | ) |
| 278 | .pass() | 280 | .pass() |
| 279 | } | 281 | } |
| 282 | |||
| 283 | /// Test that SHA1 capabilities are advertised in git-upload-pack | ||
| 284 | /// | ||
| 285 | /// GRASP-01 requires: | ||
| 286 | /// "MUST include `allow-reachable-sha1-in-want` and `allow-tip-sha1-in-want` | ||
| 287 | /// in advertisement and serve available oids." | ||
| 288 | /// | ||
| 289 | /// This test verifies: | ||
| 290 | /// 1. The info/refs endpoint returns the capabilities | ||
| 291 | /// 2. Both allow-reachable-sha1-in-want and allow-tip-sha1-in-want are present | ||
| 292 | pub async fn test_sha1_capabilities_advertised( | ||
| 293 | client: &AuditClient, | ||
| 294 | git_data_dir: &Path, | ||
| 295 | relay_domain: &str, | ||
| 296 | ) -> TestResult { | ||
| 297 | let test_name = "test_sha1_capabilities_advertised"; | ||
| 298 | let ctx = TestContext::new(client); | ||
| 299 | |||
| 300 | // Create repository announcement | ||
| 301 | let repo = match ctx.get_fixture(FixtureKind::ValidRepo).await { | ||
| 302 | Ok(r) => r, | ||
| 303 | Err(e) => { | ||
| 304 | return TestResult::new( | ||
| 305 | test_name, | ||
| 306 | "GRASP-01", | ||
| 307 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 308 | ) | ||
| 309 | .fail(&format!("Failed to create repo fixture: {}", e)) | ||
| 310 | } | ||
| 311 | }; | ||
| 312 | |||
| 313 | // Wait for repository creation | ||
| 314 | tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
| 315 | |||
| 316 | // Extract repo identifier and npub | ||
| 317 | let repo_id = match repo | ||
| 318 | .tags | ||
| 319 | .iter() | ||
| 320 | .find(|t| t.kind() == TagKind::d()) | ||
| 321 | .and_then(|t| t.content()) | ||
| 322 | { | ||
| 323 | Some(id) => id.to_string(), | ||
| 324 | None => { | ||
| 325 | return TestResult::new( | ||
| 326 | test_name, | ||
| 327 | "GRASP-01", | ||
| 328 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 329 | ) | ||
| 330 | .fail("Repository announcement missing d tag") | ||
| 331 | } | ||
| 332 | }; | ||
| 333 | |||
| 334 | let npub = match repo.pubkey.to_bech32() { | ||
| 335 | Ok(n) => n, | ||
| 336 | Err(e) => { | ||
| 337 | return TestResult::new( | ||
| 338 | test_name, | ||
| 339 | "GRASP-01", | ||
| 340 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 341 | ) | ||
| 342 | .fail(&format!("Failed to convert pubkey to npub: {}", e)) | ||
| 343 | } | ||
| 344 | }; | ||
| 345 | |||
| 346 | // Verify repository exists | ||
| 347 | let repo_path = git_data_dir.join(&npub).join(format!("{}.git", repo_id)); | ||
| 348 | if !repo_path.exists() { | ||
| 349 | return TestResult::new( | ||
| 350 | test_name, | ||
| 351 | "GRASP-01", | ||
| 352 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 353 | ) | ||
| 354 | .fail(&format!( | ||
| 355 | "Repository not found at: {}", | ||
| 356 | repo_path.display() | ||
| 357 | )); | ||
| 358 | } | ||
| 359 | |||
| 360 | // Build info/refs URL for git-upload-pack service | ||
| 361 | let info_refs_url = format!( | ||
| 362 | "http://{}/{}/{}.git/info/refs?service=git-upload-pack", | ||
| 363 | relay_domain, npub, repo_id | ||
| 364 | ); | ||
| 365 | |||
| 366 | // Make HTTP request to get the advertisement | ||
| 367 | let http_client = reqwest::Client::new(); | ||
| 368 | let response = match http_client.get(&info_refs_url).send().await { | ||
| 369 | Ok(r) => r, | ||
| 370 | Err(e) => { | ||
| 371 | return TestResult::new( | ||
| 372 | test_name, | ||
| 373 | "GRASP-01", | ||
| 374 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 375 | ) | ||
| 376 | .fail(&format!("HTTP request failed: {}", e)) | ||
| 377 | } | ||
| 378 | }; | ||
| 379 | |||
| 380 | if !response.status().is_success() { | ||
| 381 | return TestResult::new( | ||
| 382 | test_name, | ||
| 383 | "GRASP-01", | ||
| 384 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 385 | ) | ||
| 386 | .fail(&format!( | ||
| 387 | "info/refs request failed with status: {}", | ||
| 388 | response.status() | ||
| 389 | )); | ||
| 390 | } | ||
| 391 | |||
| 392 | // Get response body | ||
| 393 | let body = match response.text().await { | ||
| 394 | Ok(b) => b, | ||
| 395 | Err(e) => { | ||
| 396 | return TestResult::new( | ||
| 397 | test_name, | ||
| 398 | "GRASP-01", | ||
| 399 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 400 | ) | ||
| 401 | .fail(&format!("Failed to read response body: {}", e)) | ||
| 402 | } | ||
| 403 | }; | ||
| 404 | |||
| 405 | // Check for required capabilities | ||
| 406 | let has_allow_reachable = body.contains("allow-reachable-sha1-in-want"); | ||
| 407 | let has_allow_tip = body.contains("allow-tip-sha1-in-want"); | ||
| 408 | |||
| 409 | if !has_allow_reachable { | ||
| 410 | return TestResult::new( | ||
| 411 | test_name, | ||
| 412 | "GRASP-01", | ||
| 413 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 414 | ) | ||
| 415 | .fail("Missing capability: allow-reachable-sha1-in-want"); | ||
| 416 | } | ||
| 417 | |||
| 418 | if !has_allow_tip { | ||
| 419 | return TestResult::new( | ||
| 420 | test_name, | ||
| 421 | "GRASP-01", | ||
| 422 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 423 | ) | ||
| 424 | .fail("Missing capability: allow-tip-sha1-in-want"); | ||
| 425 | } | ||
| 426 | |||
| 427 | TestResult::new( | ||
| 428 | test_name, | ||
| 429 | "GRASP-01", | ||
| 430 | "MUST include allow-reachable-sha1-in-want and allow-tip-sha1-in-want in advertisement", | ||
| 431 | ) | ||
| 432 | .pass() | ||
| 433 | } | ||
| 280 | } | 434 | } |
| 281 | 435 | ||
| 282 | #[cfg(test)] | 436 | #[cfg(test)] |