upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/grasp-audit/src/specs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-12-01 21:48:24 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-12-01 21:48:24 +0000
commit883774c7785c57bded21ba3be63d0fdac8a51dcf (patch)
treefd37121307bd8719107e936cb3bc507499dd7ba3 /grasp-audit/src/specs
parent3c398b5e528f79231fa55f91225f9e79be1d43f5 (diff)
better fixtures: RecursiveMaintainerStateDataPushed and test_non_maintainer_state_rejected
Diffstat (limited to 'grasp-audit/src/specs')
-rw-r--r--grasp-audit/src/specs/grasp01/push_authorization.rs225
1 files changed, 54 insertions, 171 deletions
diff --git a/grasp-audit/src/specs/grasp01/push_authorization.rs b/grasp-audit/src/specs/grasp01/push_authorization.rs
index 2e14953..36abd30 100644
--- a/grasp-audit/src/specs/grasp01/push_authorization.rs
+++ b/grasp-audit/src/specs/grasp01/push_authorization.rs
@@ -781,7 +781,7 @@ impl PushAuthorizationTests {
781 /// 4. **DataPushed**: Clones repo, creates maintainer deterministic commit, pushes to relay 781 /// 4. **DataPushed**: Clones repo, creates maintainer deterministic commit, pushes to relay
782 /// 782 ///
783 /// The test wraps the fixture result in pass/fail using the error message. 783 /// The test wraps the fixture result in pass/fail using the error message.
784 #[allow(unused_variables)] // relay_domain is now handled by fixture 784 #[allow(unused_variables)] // relay_domain is now handled by fixture
785 pub async fn test_push_authorized_by_maintainer_state_only( 785 pub async fn test_push_authorized_by_maintainer_state_only(
786 client: &AuditClient, 786 client: &AuditClient,
787 relay_domain: &str, 787 relay_domain: &str,
@@ -791,23 +791,22 @@ impl PushAuthorizationTests {
791 791
792 // The MaintainerStateDataPushed fixture handles all stages: 792 // The MaintainerStateDataPushed fixture handles all stages:
793 // Generate → Send → Verify → DataPush 793 // Generate → Send → Verify → DataPush
794 match ctx.get_fixture(FixtureKind::MaintainerStateDataPushed).await { 794 match ctx
795 Ok(_maintainer_state_event) => { 795 .get_fixture(FixtureKind::MaintainerStateDataPushed)
796 TestResult::new( 796 .await
797 test_name, 797 {
798 "GRASP-01", 798 Ok(_maintainer_state_event) => TestResult::new(
799 "Push authorized by maintainer state event only (no announcement)", 799 test_name,
800 ) 800 "GRASP-01",
801 .pass() 801 "Push authorized by maintainer state event only (no announcement)",
802 } 802 )
803 Err(e) => { 803 .pass(),
804 TestResult::new( 804 Err(e) => TestResult::new(
805 test_name, 805 test_name,
806 "GRASP-01", 806 "GRASP-01",
807 "Push authorized by maintainer state event only (no announcement)", 807 "Push authorized by maintainer state event only (no announcement)",
808 ) 808 )
809 .fail(format!("{}", e)) 809 .fail(format!("{}", e)),
810 }
811 } 810 }
812 } 811 }
813 812
@@ -825,7 +824,7 @@ impl PushAuthorizationTests {
825 /// 4. **DataPushed**: Clones repo, creates recursive maintainer deterministic commit, pushes to relay 824 /// 4. **DataPushed**: Clones repo, creates recursive maintainer deterministic commit, pushes to relay
826 /// 825 ///
827 /// The test wraps the fixture result in pass/fail using the error message. 826 /// The test wraps the fixture result in pass/fail using the error message.
828 #[allow(unused_variables)] // relay_domain is now handled by fixture 827 #[allow(unused_variables)] // relay_domain is now handled by fixture
829 pub async fn test_push_authorized_by_recursive_maintainer_state( 828 pub async fn test_push_authorized_by_recursive_maintainer_state(
830 client: &AuditClient, 829 client: &AuditClient,
831 relay_domain: &str, 830 relay_domain: &str,
@@ -835,23 +834,22 @@ impl PushAuthorizationTests {
835 834
836 // The RecursiveMaintainerStateDataPushed fixture handles all stages: 835 // The RecursiveMaintainerStateDataPushed fixture handles all stages:
837 // Generate → Send → Verify → DataPush 836 // Generate → Send → Verify → DataPush
838 match ctx.get_fixture(FixtureKind::RecursiveMaintainerStateDataPushed).await { 837 match ctx
839 Ok(_recursive_maintainer_state_event) => { 838 .get_fixture(FixtureKind::RecursiveMaintainerStateDataPushed)
840 TestResult::new( 839 .await
841 test_name, 840 {
842 "GRASP-01", 841 Ok(_recursive_maintainer_state_event) => TestResult::new(
843 "Push authorized by recursive maintainer state event", 842 test_name,
844 ) 843 "GRASP-01",
845 .pass() 844 "Push authorized by recursive maintainer state event",
846 } 845 )
847 Err(e) => { 846 .pass(),
848 TestResult::new( 847 Err(e) => TestResult::new(
849 test_name, 848 test_name,
850 "GRASP-01", 849 "GRASP-01",
851 "Push authorized by recursive maintainer state event", 850 "Push authorized by recursive maintainer state event",
852 ) 851 )
853 .fail(format!("{}", e)) 852 .fail(format!("{}", e)),
854 }
855 } 853 }
856 } 854 }
857 855
@@ -860,28 +858,34 @@ impl PushAuthorizationTests {
860 /// GRASP-01: "respecting the recursive maintainer set" 858 /// GRASP-01: "respecting the recursive maintainer set"
861 /// (Conversely, state events from non-maintainers MUST be ignored) 859 /// (Conversely, state events from non-maintainers MUST be ignored)
862 /// 860 ///
863 /// ## Fixture-First Pattern 861 /// ## Fixture Compatibility
862 ///
863 /// This test is compatible with any descendant of `OwnerStateDataPushed`:
864 /// - `OwnerStateDataPushed` - owner's state event with git data pushed
865 /// - `MaintainerStateDataPushed` - maintainer's state event with git data pushed
866 /// - `RecursiveMaintainerStateDataPushed` - recursive maintainer's state event with git data pushed
867 ///
868 /// All of these establish valid state on the relay that a non-maintainer should NOT be able to override.
864 /// 869 ///
865 /// 1. **Generate**: Create TestContext and get RepoState fixture 870 /// ## Test Flow
866 /// (repo announcement + state event pointing to deterministic commit) 871 ///
867 /// 2. **Send**: Clone repo, create deterministic commit, push (establishes state on relay) 872 /// 1. **Setup**: Get OwnerStateDataPushed fixture (repo + state event + git data pushed)
868 /// 3. **Attack**: Create a rogue state event signed by a non-maintainer 873 /// 2. **Clone**: Fresh clone of the repository
869 /// 4. **Test**: Create a new commit and try to push 874 /// 3. **Attack**: Create a new commit and a rogue state event signed by a non-maintainer
870 /// 5. **Verify**: Push should be rejected because rogue state event is ignored 875 /// 4. **Verify**: Push should be rejected because rogue state event is ignored
871 pub async fn test_non_maintainer_state_rejected( 876 pub async fn test_non_maintainer_state_rejected(
872 client: &AuditClient, 877 client: &AuditClient,
873 relay_domain: &str, 878 relay_domain: &str,
874 ) -> TestResult { 879 ) -> TestResult {
875 use std::process::Command;
876
877 let test_name = "test_non_maintainer_state_rejected"; 880 let test_name = "test_non_maintainer_state_rejected";
878 881
879 // ============================================================ 882 // ============================================================
880 // Step 1: GENERATE - Create TestContext and get RepoState fixture 883 // Step 1: SETUP - Get OwnerStateDataPushed fixture
884 // This establishes valid state on the relay with git data
881 // ============================================================ 885 // ============================================================
882 let ctx = TestContext::new(client); 886 let ctx = TestContext::new(client);
883 887
884 let state_event = match ctx.get_fixture(FixtureKind::RepoState).await { 888 let state_event = match ctx.get_fixture(FixtureKind::OwnerStateDataPushed).await {
885 Ok(e) => e, 889 Ok(e) => e,
886 Err(e) => { 890 Err(e) => {
887 return TestResult::new( 891 return TestResult::new(
@@ -889,12 +893,10 @@ impl PushAuthorizationTests {
889 "GRASP-01", 893 "GRASP-01",
890 "Non-maintainer state events ignored", 894 "Non-maintainer state events ignored",
891 ) 895 )
892 .fail(format!("Failed to create RepoState fixture: {}", e)); 896 .fail(format!("Failed to get OwnerStateDataPushed fixture: {}", e));
893 } 897 }
894 }; 898 };
895 899
896 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
897
898 // Extract repo_id and npub from state event 900 // Extract repo_id and npub from state event
899 let repo_id = match state_event 901 let repo_id = match state_event
900 .tags 902 .tags
@@ -926,8 +928,7 @@ impl PushAuthorizationTests {
926 }; 928 };
927 929
928 // ============================================================ 930 // ============================================================
929 // Step 2: SEND - Clone repo, create deterministic commit, push 931 // Step 2: CLONE - Fresh clone of the repository
930 // (establishes the state on the relay)
931 // ============================================================ 932 // ============================================================
932 let clone_path = match clone_repo(relay_domain, &npub, &repo_id) { 933 let clone_path = match clone_repo(relay_domain, &npub, &repo_id) {
933 Ok(p) => p, 934 Ok(p) => p,
@@ -946,124 +947,6 @@ impl PushAuthorizationTests {
946 let _ = fs::remove_dir_all(&clone_path); 947 let _ = fs::remove_dir_all(&clone_path);
947 }; 948 };
948 949
949 // Create deterministic commit locally
950 let commit_hash = match create_deterministic_commit(&clone_path, "Initial commit") {
951 Ok(h) => h,
952 Err(e) => {
953 cleanup();
954 return TestResult::new(
955 test_name,
956 "GRASP-01",
957 "Non-maintainer state events ignored",
958 )
959 .fail(format!("Failed to create deterministic commit: {}", e));
960 }
961 };
962
963 // Verify commit hash matches expected
964 if commit_hash != DETERMINISTIC_COMMIT_HASH {
965 cleanup();
966 return TestResult::new(test_name, "GRASP-01", "Non-maintainer state events ignored")
967 .fail(format!(
968 "Commit hash mismatch: got {}, expected {}",
969 commit_hash, DETERMINISTIC_COMMIT_HASH
970 ));
971 }
972
973 // Create main branch pointing to our deterministic commit
974 let branch_output = Command::new("git")
975 .args(["branch", "main"])
976 .current_dir(&clone_path)
977 .output();
978
979 match branch_output {
980 Err(e) => {
981 cleanup();
982 return TestResult::new(
983 test_name,
984 "GRASP-01",
985 "Non-maintainer state events ignored",
986 )
987 .fail(format!("Failed to create main branch: {}", e));
988 }
989 Ok(output) if !output.status.success() => {
990 cleanup();
991 return TestResult::new(
992 test_name,
993 "GRASP-01",
994 "Non-maintainer state events ignored",
995 )
996 .fail(format!(
997 "Failed to create main branch: {}",
998 String::from_utf8_lossy(&output.stderr)
999 ));
1000 }
1001 _ => {}
1002 }
1003
1004 // Checkout main branch
1005 let checkout_output = Command::new("git")
1006 .args(["checkout", "main"])
1007 .current_dir(&clone_path)
1008 .output();
1009
1010 match checkout_output {
1011 Err(e) => {
1012 cleanup();
1013 return TestResult::new(
1014 test_name,
1015 "GRASP-01",
1016 "Non-maintainer state events ignored",
1017 )
1018 .fail(format!("Failed to checkout main branch: {}", e));
1019 }
1020 Ok(output) if !output.status.success() => {
1021 cleanup();
1022 return TestResult::new(
1023 test_name,
1024 "GRASP-01",
1025 "Non-maintainer state events ignored",
1026 )
1027 .fail(format!(
1028 "Failed to checkout main branch: {}",
1029 String::from_utf8_lossy(&output.stderr)
1030 ));
1031 }
1032 _ => {}
1033 }
1034
1035 // Push the deterministic commit to establish state on relay
1036 let push_output = Command::new("git")
1037 .args(["push", "origin", "main"])
1038 .current_dir(&clone_path)
1039 .env("GIT_TERMINAL_PROMPT", "0")
1040 .output();
1041
1042 match push_output {
1043 Err(e) => {
1044 cleanup();
1045 return TestResult::new(
1046 test_name,
1047 "GRASP-01",
1048 "Non-maintainer state events ignored",
1049 )
1050 .fail(format!("Failed to push initial commit: {}", e));
1051 }
1052 Ok(output) if !output.status.success() => {
1053 cleanup();
1054 return TestResult::new(
1055 test_name,
1056 "GRASP-01",
1057 "Non-maintainer state events ignored",
1058 )
1059 .fail(format!(
1060 "Failed to push initial commit: {}",
1061 String::from_utf8_lossy(&output.stderr)
1062 ));
1063 }
1064 _ => {}
1065 }
1066
1067 // ============================================================ 950 // ============================================================
1068 // Step 3: ATTACK - Create a new commit and a rogue state event 951 // Step 3: ATTACK - Create a new commit and a rogue state event
1069 // from a non-maintainer 952 // from a non-maintainer
@@ -1118,7 +1001,7 @@ impl PushAuthorizationTests {
1118 tokio::time::sleep(std::time::Duration::from_millis(200)).await; 1001 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
1119 1002
1120 // ============================================================ 1003 // ============================================================
1121 // Step 4 & 5: VERIFY - Push should be rejected because rogue 1004 // Step 4: VERIFY - Push should be rejected because rogue
1122 // state event is ignored 1005 // state event is ignored
1123 // ============================================================ 1006 // ============================================================
1124 let push_result = try_push(&clone_path); 1007 let push_result = try_push(&clone_path);