@@ -66,6 +66,32 @@ pub struct TemporaryAuthorizationRecord { |
| 66 | 66 | pub expires_at_unix: u64, |
| 67 | 67 | pub expires_in_secs: u64, |
| 68 | 68 | } |
| 69 | + |
| 70 | +#[derive(Debug, Clone, serde::Serialize)] |
| 71 | +pub struct SubjectResolution { |
| 72 | + pub kind: String, |
| 73 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 74 | + pub session_id: Option<String>, |
| 75 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 76 | + pub pid: Option<u32>, |
| 77 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 78 | + pub uid: Option<u32>, |
| 79 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 80 | + pub start_time_ticks: Option<u64>, |
| 81 | + pub has_start_time: bool, |
| 82 | +} |
| 83 | + |
| 84 | +#[derive(Debug, Clone, serde::Serialize)] |
| 85 | +pub struct AuthorityDiagnostics { |
| 86 | + pub authority_connected: bool, |
| 87 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 88 | + pub authority_error: Option<String>, |
| 89 | + pub subject: SubjectResolution, |
| 90 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 91 | + pub temporary_authorization_count: Option<usize>, |
| 92 | + #[serde(skip_serializing_if = "Option::is_none")] |
| 93 | + pub temporary_authorization_error: Option<String>, |
| 94 | +} |
| 69 | 95 | const DEFAULT_AUTH_MAX_ATTEMPTS: usize = 3; |
| 70 | 96 | const DEFAULT_IDENTITY_SELECTION_ATTEMPTS: usize = 3; |
| 71 | 97 | const DEFAULT_RETENTION_SELECTION_ATTEMPTS: usize = 3; |
@@ -672,28 +698,7 @@ fn helper_outcome_label(outcome: HelperOutcome) -> &'static str { |
| 672 | 698 | pub fn enumerate_temporary_authorizations() -> Result<Vec<TemporaryAuthorizationRecord>> { |
| 673 | 699 | let connection = Connection::system().context("failed to connect to system bus")?; |
| 674 | 700 | let subject = build_subject(); |
| 675 | | - let proxy = PolkitAgent::proxy(&connection)?; |
| 676 | | - let authorizations: Vec<TemporaryAuthorization> = |
| 677 | | - proxy.call("EnumerateTemporaryAuthorizations", &subject)?; |
| 678 | | - let now_unix = SystemTime::now() |
| 679 | | - .duration_since(UNIX_EPOCH) |
| 680 | | - .map(|duration| duration.as_secs()) |
| 681 | | - .unwrap_or(0); |
| 682 | | - |
| 683 | | - let mut entries = Vec::with_capacity(authorizations.len()); |
| 684 | | - for (authorization_id, action_id, _subject, obtained_at_unix, expires_at_unix) in authorizations |
| 685 | | - { |
| 686 | | - let expires_in_secs = expires_at_unix.saturating_sub(now_unix); |
| 687 | | - entries.push(TemporaryAuthorizationRecord { |
| 688 | | - authorization_id, |
| 689 | | - action_id, |
| 690 | | - obtained_at_unix, |
| 691 | | - expires_at_unix, |
| 692 | | - expires_in_secs, |
| 693 | | - }); |
| 694 | | - } |
| 695 | | - entries.sort_by(|left, right| left.action_id.cmp(&right.action_id)); |
| 696 | | - Ok(entries) |
| 701 | + enumerate_temporary_authorizations_for_subject(&connection, &subject) |
| 697 | 702 | } |
| 698 | 703 | |
| 699 | 704 | pub fn revoke_temporary_authorization_by_id(authorization_id: &str) -> Result<()> { |
@@ -739,6 +744,101 @@ fn revoke_temporary_authorizations_for_action(action_id: &str) -> Result<usize> |
| 739 | 744 | Ok(revoked) |
| 740 | 745 | } |
| 741 | 746 | |
| 747 | +pub fn current_subject_resolution() -> SubjectResolution { |
| 748 | + if let Some(session_id) = current_session_id() { |
| 749 | + return SubjectResolution { |
| 750 | + kind: "unix-session".to_string(), |
| 751 | + session_id: Some(session_id), |
| 752 | + pid: None, |
| 753 | + uid: None, |
| 754 | + start_time_ticks: None, |
| 755 | + has_start_time: false, |
| 756 | + }; |
| 757 | + } |
| 758 | + |
| 759 | + let start_time_ticks = process_start_time_ticks(); |
| 760 | + SubjectResolution { |
| 761 | + kind: "unix-process".to_string(), |
| 762 | + session_id: None, |
| 763 | + pid: Some(std::process::id()), |
| 764 | + uid: Some(nix::unistd::geteuid().as_raw()), |
| 765 | + start_time_ticks, |
| 766 | + has_start_time: start_time_ticks.is_some(), |
| 767 | + } |
| 768 | +} |
| 769 | + |
| 770 | +pub fn collect_authority_diagnostics() -> AuthorityDiagnostics { |
| 771 | + let subject = current_subject_resolution(); |
| 772 | + let connection = match Connection::system() { |
| 773 | + Ok(connection) => connection, |
| 774 | + Err(err) => { |
| 775 | + return AuthorityDiagnostics { |
| 776 | + authority_connected: false, |
| 777 | + authority_error: Some(format!("failed to connect to system bus: {}", err)), |
| 778 | + subject, |
| 779 | + temporary_authorization_count: None, |
| 780 | + temporary_authorization_error: None, |
| 781 | + }; |
| 782 | + } |
| 783 | + }; |
| 784 | + |
| 785 | + if let Err(err) = PolkitAgent::ping_authority(&connection) { |
| 786 | + return AuthorityDiagnostics { |
| 787 | + authority_connected: false, |
| 788 | + authority_error: Some(format!("polkit authority ping failed: {}", err)), |
| 789 | + subject, |
| 790 | + temporary_authorization_count: None, |
| 791 | + temporary_authorization_error: None, |
| 792 | + }; |
| 793 | + } |
| 794 | + |
| 795 | + let polkit_subject = subject_to_polkit_subject(&subject); |
| 796 | + match enumerate_temporary_authorizations_for_subject(&connection, &polkit_subject) { |
| 797 | + Ok(authorizations) => AuthorityDiagnostics { |
| 798 | + authority_connected: true, |
| 799 | + authority_error: None, |
| 800 | + subject, |
| 801 | + temporary_authorization_count: Some(authorizations.len()), |
| 802 | + temporary_authorization_error: None, |
| 803 | + }, |
| 804 | + Err(err) => AuthorityDiagnostics { |
| 805 | + authority_connected: true, |
| 806 | + authority_error: None, |
| 807 | + subject, |
| 808 | + temporary_authorization_count: None, |
| 809 | + temporary_authorization_error: Some(err.to_string()), |
| 810 | + }, |
| 811 | + } |
| 812 | +} |
| 813 | + |
| 814 | +fn enumerate_temporary_authorizations_for_subject( |
| 815 | + connection: &Connection, |
| 816 | + subject: &Subject, |
| 817 | +) -> Result<Vec<TemporaryAuthorizationRecord>> { |
| 818 | + let proxy = PolkitAgent::proxy(connection)?; |
| 819 | + let authorizations: Vec<TemporaryAuthorization> = |
| 820 | + proxy.call("EnumerateTemporaryAuthorizations", subject)?; |
| 821 | + let now_unix = SystemTime::now() |
| 822 | + .duration_since(UNIX_EPOCH) |
| 823 | + .map(|duration| duration.as_secs()) |
| 824 | + .unwrap_or(0); |
| 825 | + |
| 826 | + let mut entries = Vec::with_capacity(authorizations.len()); |
| 827 | + for (authorization_id, action_id, _subject, obtained_at_unix, expires_at_unix) in authorizations |
| 828 | + { |
| 829 | + let expires_in_secs = expires_at_unix.saturating_sub(now_unix); |
| 830 | + entries.push(TemporaryAuthorizationRecord { |
| 831 | + authorization_id, |
| 832 | + action_id, |
| 833 | + obtained_at_unix, |
| 834 | + expires_at_unix, |
| 835 | + expires_in_secs, |
| 836 | + }); |
| 837 | + } |
| 838 | + entries.sort_by(|left, right| left.action_id.cmp(&right.action_id)); |
| 839 | + Ok(entries) |
| 840 | +} |
| 841 | + |
| 742 | 842 | fn render_prompt_context(request: &ActiveRequest) -> String { |
| 743 | 843 | let mut lines = Vec::new(); |
| 744 | 844 | let message = request.message.trim(); |
@@ -1506,6 +1606,14 @@ fn build_subject() -> Subject { |
| 1506 | 1606 | subject_from_session_id(current_session_id()) |
| 1507 | 1607 | } |
| 1508 | 1608 | |
| 1609 | +fn subject_to_polkit_subject(subject: &SubjectResolution) -> Subject { |
| 1610 | + if subject.kind == "unix-session" { |
| 1611 | + return subject_from_session_id(subject.session_id.clone()); |
| 1612 | + } |
| 1613 | + |
| 1614 | + build_unix_process_subject() |
| 1615 | +} |
| 1616 | + |
| 1509 | 1617 | fn subject_from_session_id(session_id: Option<String>) -> Subject { |
| 1510 | 1618 | if let Some(session_id) = session_id { |
| 1511 | 1619 | let mut details = HashMap::new(); |
@@ -1693,6 +1801,43 @@ mod tests { |
| 1693 | 1801 | assert!(subject.1.contains_key("start-time")); |
| 1694 | 1802 | } |
| 1695 | 1803 | |
| 1804 | + #[test] |
| 1805 | + fn subject_to_polkit_subject_uses_session_resolution() { |
| 1806 | + let resolution = SubjectResolution { |
| 1807 | + kind: "unix-session".to_string(), |
| 1808 | + session_id: Some("42".to_string()), |
| 1809 | + pid: None, |
| 1810 | + uid: None, |
| 1811 | + start_time_ticks: None, |
| 1812 | + has_start_time: false, |
| 1813 | + }; |
| 1814 | + |
| 1815 | + let subject = subject_to_polkit_subject(&resolution); |
| 1816 | + assert_eq!(subject.0.as_str(), "unix-session"); |
| 1817 | + let session_id = subject |
| 1818 | + .1 |
| 1819 | + .get("session-id") |
| 1820 | + .and_then(|value| <&str>::try_from(value).ok()); |
| 1821 | + assert_eq!(session_id, Some("42")); |
| 1822 | + } |
| 1823 | + |
| 1824 | + #[test] |
| 1825 | + fn subject_to_polkit_subject_falls_back_to_unix_process_resolution() { |
| 1826 | + let resolution = SubjectResolution { |
| 1827 | + kind: "unix-process".to_string(), |
| 1828 | + session_id: None, |
| 1829 | + pid: Some(std::process::id()), |
| 1830 | + uid: Some(nix::unistd::geteuid().as_raw()), |
| 1831 | + start_time_ticks: process_start_time_ticks(), |
| 1832 | + has_start_time: true, |
| 1833 | + }; |
| 1834 | + |
| 1835 | + let subject = subject_to_polkit_subject(&resolution); |
| 1836 | + assert_eq!(subject.0.as_str(), "unix-process"); |
| 1837 | + assert!(subject.1.contains_key("pid")); |
| 1838 | + assert!(subject.1.contains_key("uid")); |
| 1839 | + } |
| 1840 | + |
| 1696 | 1841 | #[test] |
| 1697 | 1842 | fn invalid_object_path_error_message_mentions_path() { |
| 1698 | 1843 | let err = match PolkitAgent::new( |