gardesk/garcard / 9559b3f

Browse files

Add terminal auth outcome acceptance matrix

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
9559b3fefabf13ab377734b841cdd36cd6115563
Parents
144bd05
Tree
1bab78a

1 changed file

StatusFile+-
M garcard/src/agent.rs 141 3
garcard/src/agent.rsmodified
@@ -417,7 +417,11 @@ impl PolkitRuntime {
417417
                     action_id = %request.action_id,
418418
                     "Authentication request canceled before helper attempt"
419419
                 );
420
-                return self.finalize_auth_attempt(request, HelperOutcome::Canceled, Some(retention));
420
+                return self.finalize_auth_attempt(
421
+                    request,
422
+                    HelperOutcome::Canceled,
423
+                    Some(retention),
424
+                );
421425
             }
422426
             tracing::info!(
423427
                 context = %prompt_context,
@@ -1889,6 +1893,137 @@ mod tests {
18891893
         let _ = std::fs::remove_file(&socket_path);
18901894
     }
18911895
 
1896
+    #[test]
1897
+    fn authenticate_active_request_terminal_outcome_matrix() {
1898
+        struct Case {
1899
+            label: &'static str,
1900
+            responses: Vec<PromptResponse>,
1901
+            server_results: Vec<Option<&'static str>>,
1902
+            expected_outcome: HelperOutcome,
1903
+            expected_success_count: usize,
1904
+            expected_failure_count: usize,
1905
+        }
1906
+
1907
+        let cases = vec![
1908
+            Case {
1909
+                label: "success",
1910
+                responses: vec![PromptResponse::Submitted("correct horse".to_string())],
1911
+                server_results: vec![Some("SUCCESS")],
1912
+                expected_outcome: HelperOutcome::Authorized,
1913
+                expected_success_count: 1,
1914
+                expected_failure_count: 0,
1915
+            },
1916
+            Case {
1917
+                label: "failure",
1918
+                responses: vec![
1919
+                    PromptResponse::Submitted("wrong horse".to_string()),
1920
+                    PromptResponse::Submitted("wrong horse".to_string()),
1921
+                    PromptResponse::Submitted("wrong horse".to_string()),
1922
+                ],
1923
+                server_results: vec![Some("FAILURE"), Some("FAILURE"), Some("FAILURE")],
1924
+                expected_outcome: HelperOutcome::Denied,
1925
+                expected_success_count: 0,
1926
+                expected_failure_count: 3,
1927
+            },
1928
+            Case {
1929
+                label: "canceled",
1930
+                responses: vec![PromptResponse::Canceled],
1931
+                server_results: vec![None],
1932
+                expected_outcome: HelperOutcome::Canceled,
1933
+                expected_success_count: 0,
1934
+                expected_failure_count: 0,
1935
+            },
1936
+            Case {
1937
+                label: "timeout",
1938
+                responses: vec![PromptResponse::TimedOut],
1939
+                server_results: vec![None],
1940
+                expected_outcome: HelperOutcome::Timeout,
1941
+                expected_success_count: 0,
1942
+                expected_failure_count: 0,
1943
+            },
1944
+        ];
1945
+
1946
+        for case in cases {
1947
+            let socket_path = temp_socket_path();
1948
+            let listener = UnixListener::bind(&socket_path).expect("bind test socket");
1949
+            let server_results = case.server_results.clone();
1950
+
1951
+            let server = thread::spawn(move || {
1952
+                for expected_result in server_results {
1953
+                    let (mut stream, _) = listener.accept().expect("accept");
1954
+                    let read_stream = stream.try_clone().expect("clone");
1955
+                    let mut reader = BufReader::new(read_stream);
1956
+
1957
+                    let mut first_line = String::new();
1958
+                    reader.read_line(&mut first_line).expect("read first line");
1959
+                    if first_line.trim() == "operator" {
1960
+                        let mut cookie = String::new();
1961
+                        reader.read_line(&mut cookie).expect("read cookie");
1962
+                    }
1963
+
1964
+                    stream
1965
+                        .write_all(b"PAM_PROMPT_ECHO_OFF Password:\n")
1966
+                        .expect("write prompt");
1967
+                    stream.flush().expect("flush prompt");
1968
+
1969
+                    let mut secret = String::new();
1970
+                    let read = reader.read_line(&mut secret).expect("read secret");
1971
+                    if let Some(result) = expected_result {
1972
+                        assert!(
1973
+                            read > 0,
1974
+                            "expected submitted secret response for {}",
1975
+                            result
1976
+                        );
1977
+                        stream
1978
+                            .write_all(format!("{}\n", result).as_bytes())
1979
+                            .expect("write result");
1980
+                        stream.flush().expect("flush result");
1981
+                    }
1982
+                }
1983
+            });
1984
+
1985
+            let runtime = PolkitRuntime {
1986
+                auth_state: Arc::new(AuthState::default()),
1987
+                queue: Mutex::new(AuthQueue::default()),
1988
+                outcomes: Mutex::new(HashMap::new()),
1989
+                canceled_cookies: Mutex::new(HashSet::new()),
1990
+                outcome_signal: Condvar::new(),
1991
+                helper_client: HelperSocketClient::new(&socket_path),
1992
+                processing: AtomicBool::new(false),
1993
+                worker_enabled: false,
1994
+            };
1995
+            let active = ActiveRequest {
1996
+                action_id: "org.gardesk.test".to_string(),
1997
+                message: "Authenticate".to_string(),
1998
+                icon_name: "dialog-password".to_string(),
1999
+                detail_count: 0,
2000
+                details: HashMap::new(),
2001
+                cookie: format!("cookie-{}", case.label),
2002
+                username: "operator".to_string(),
2003
+                identity_options: vec!["operator".to_string()],
2004
+                retention_options: vec![RetentionPolicy::OneShot],
2005
+            };
2006
+            let mut prompts = SequencedPrompt::new(case.responses);
2007
+
2008
+            let outcome = runtime.authenticate_active_request_with_prompts(&active, &mut prompts);
2009
+            assert_eq!(outcome, case.expected_outcome, "case {}", case.label);
2010
+            assert_eq!(
2011
+                prompts.success_count, case.expected_success_count,
2012
+                "case {}",
2013
+                case.label
2014
+            );
2015
+            assert_eq!(
2016
+                prompts.failure_messages.len(),
2017
+                case.expected_failure_count,
2018
+                "case {}",
2019
+                case.label
2020
+            );
2021
+
2022
+            server.join().expect("server join");
2023
+            let _ = std::fs::remove_file(&socket_path);
2024
+        }
2025
+    }
2026
+
18922027
     #[test]
18932028
     fn render_prompt_context_includes_policy_details() {
18942029
         let mut details = HashMap::new();
@@ -2039,8 +2174,11 @@ mod tests {
20392174
             retention_options: vec![RetentionPolicy::OneShot],
20402175
         };
20412176
 
2042
-        let outcome =
2043
-            runtime.finalize_auth_attempt(&request, HelperOutcome::Denied, Some(RetentionPolicy::OneShot));
2177
+        let outcome = runtime.finalize_auth_attempt(
2178
+            &request,
2179
+            HelperOutcome::Denied,
2180
+            Some(RetentionPolicy::OneShot),
2181
+        );
20442182
         assert_eq!(outcome, HelperOutcome::Denied);
20452183
 
20462184
         let summary = auth_state.summary();