Skip to main content

nextcloud_goa/
account.rs

1//! Nextcloud account discovered via GNOME Online Accounts.
2
3use std::fmt;
4
5use nextcloud_auth::BasicAuth;
6use nextcloud_sdk_core::ServerUrl;
7
8/// A Nextcloud or ownCloud account discovered via GNOME Online Accounts.
9///
10/// This type holds account metadata along with assembled [`BasicAuth`] credentials
11/// retrieved from the desktop session's secure credential store.
12#[derive(Clone)]
13pub struct GoaAccount {
14    id: String,
15    username: String,
16    server_url: ServerUrl,
17    auth: BasicAuth,
18    presentation_identity: String,
19}
20
21impl GoaAccount {
22    /// Creates a new [`GoaAccount`].
23    pub fn new(
24        id: impl Into<String>,
25        username: impl Into<String>,
26        server_url: ServerUrl,
27        auth: BasicAuth,
28        presentation_identity: impl Into<String>,
29    ) -> Self {
30        Self {
31            id: id.into(),
32            username: username.into(),
33            server_url,
34            auth,
35            presentation_identity: presentation_identity.into(),
36        }
37    }
38
39    /// The GNOME Online Accounts internal identifier (e.g. `account_1234`).
40    pub fn id(&self) -> &str {
41        &self.id
42    }
43
44    /// The user name associated with the account.
45    pub fn username(&self) -> &str {
46        &self.username
47    }
48
49    /// The base URL of the Nextcloud instance.
50    pub fn server_url(&self) -> &ServerUrl {
51        &self.server_url
52    }
53
54    /// The basic authentication credentials for API requests.
55    pub fn auth(&self) -> &BasicAuth {
56        &self.auth
57    }
58
59    /// Presentation identity suitable for displaying in a user interface.
60    pub fn presentation_identity(&self) -> &str {
61        &self.presentation_identity
62    }
63}
64
65impl fmt::Debug for GoaAccount {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        f.debug_struct("GoaAccount")
68            .field("id", &self.id)
69            .field("username", &self.username)
70            .field("server_url", &self.server_url)
71            .field("auth", &"[REDACTED]")
72            .field("presentation_identity", &self.presentation_identity)
73            .finish()
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use nextcloud_auth::{AppPassword, BasicAuth, SecretString};
80    use nextcloud_sdk_core::ServerUrl;
81
82    use super::GoaAccount;
83
84    fn sample_account() -> GoaAccount {
85        let secret = SecretString::new("super-secret-password".to_owned());
86        let creds = AppPassword::new("alice", secret);
87        let auth = BasicAuth::from_app_password(creds);
88        let url = ServerUrl::parse("https://cloud.example.com").unwrap();
89
90        GoaAccount::new(
91            "account_1234",
92            "alice",
93            url,
94            auth,
95            "alice@cloud.example.com",
96        )
97    }
98
99    #[test]
100    fn getters_return_expected_values() {
101        let account = sample_account();
102
103        assert_eq!(account.id(), "account_1234");
104        assert_eq!(account.username(), "alice");
105        assert_eq!(account.server_url().as_str(), "https://cloud.example.com");
106        assert_eq!(account.presentation_identity(), "alice@cloud.example.com");
107        assert_eq!(account.auth().credentials().user(), "alice");
108    }
109
110    #[test]
111    fn debug_redacts_auth_and_secrets() {
112        let account = sample_account();
113        let debug_repr = format!("{account:?}");
114
115        assert!(debug_repr.contains("account_1234"));
116        assert!(debug_repr.contains("alice"));
117        assert!(debug_repr.contains("https://cloud.example.com"));
118        assert!(debug_repr.contains("alice@cloud.example.com"));
119        assert!(debug_repr.contains("[REDACTED]"));
120
121        // Ensure password plaintext or basic auth base64 never appears
122        assert!(!debug_repr.contains("super-secret-password"));
123        assert!(!debug_repr.contains("Basic "));
124    }
125}