fxa_client/internal/oauth/
attached_clients.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5pub use super::super::http_client::GetAttachedClientResponse as AttachedClient;
6use super::super::{util, CachedResponse, FirefoxAccount};
7use crate::{Error, Result};
8
9// An attached clients response is considered fresh for `ATTACHED_CLIENTS_FRESHNESS_THRESHOLD` ms.
10const ATTACHED_CLIENTS_FRESHNESS_THRESHOLD: u64 = 60_000; // 1 minute
11
12impl FirefoxAccount {
13    /// Fetches the list of attached clients connected to the current account.
14    pub fn get_attached_clients(&mut self) -> Result<Vec<AttachedClient>> {
15        if let Some(a) = &self.attached_clients_cache {
16            if util::now() < a.cached_at + ATTACHED_CLIENTS_FRESHNESS_THRESHOLD {
17                return Ok(a.response.clone());
18            }
19        }
20        let session_token = self.get_session_token()?;
21        let response = self
22            .client
23            .get_attached_clients(self.state.config(), &session_token)?;
24
25        self.attached_clients_cache = Some(CachedResponse {
26            response: response.clone(),
27            cached_at: util::now(),
28            etag: "".into(),
29        });
30
31        Ok(response)
32    }
33}
34
35impl TryFrom<AttachedClient> for crate::AttachedClient {
36    type Error = Error;
37    fn try_from(c: AttachedClient) -> Result<Self> {
38        Ok(crate::AttachedClient {
39            client_id: c.client_id,
40            device_id: c.device_id,
41            device_type: c.device_type,
42            is_current_session: c.is_current_session,
43            name: c.name,
44            created_time: c.created_time.map(TryInto::try_into).transpose()?,
45            last_access_time: c.last_access_time.map(TryInto::try_into).transpose()?,
46            scope: c.scope,
47        })
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crate::internal::{config::Config, http_client::MockFxAClient};
55    use mockall::predicate::always;
56    use mockall::predicate::eq;
57    use std::sync::Arc;
58    use sync15::DeviceType;
59
60    #[test]
61    fn test_get_attached_clients() {
62        let config = Config::stable_dev("12345678", "https://foo.bar");
63        let mut fxa = FirefoxAccount::with_config(config);
64        fxa.set_session_token("session");
65
66        let mut client = MockFxAClient::new();
67        client
68            .expect_get_attached_clients()
69            .with(always(), eq("session"))
70            .times(1)
71            .returning(|_, _| {
72                Ok(vec![AttachedClient {
73                    client_id: Some("12345678".into()),
74                    session_token_id: None,
75                    refresh_token_id: None,
76                    device_id: None,
77                    device_type: DeviceType::Desktop,
78                    is_current_session: true,
79                    name: None,
80                    created_time: None,
81                    last_access_time: None,
82                    scope: None,
83                    user_agent: "attachedClientsUserAgent".into(),
84                    os: None,
85                }])
86            });
87
88        fxa.set_client(Arc::new(client));
89        assert!(fxa.attached_clients_cache.is_none());
90
91        let res = fxa.get_attached_clients();
92
93        assert!(res.is_ok());
94        assert!(fxa.attached_clients_cache.is_some());
95
96        let cached_attached_clients_res = fxa.attached_clients_cache.unwrap();
97        assert!(!cached_attached_clients_res.response.is_empty());
98        assert!(cached_attached_clients_res.cached_at > 0);
99
100        let cached_attached_clients = &cached_attached_clients_res.response[0];
101        assert_eq!(
102            cached_attached_clients.clone().client_id.unwrap(),
103            "12345678".to_string()
104        );
105    }
106
107    #[test]
108    fn test_get_attached_clients_network_errors() {
109        let config = Config::stable_dev("12345678", "https://foo.bar");
110        let mut fxa = FirefoxAccount::with_config(config);
111        fxa.set_session_token("session");
112
113        let mut client = MockFxAClient::new();
114        client
115            .expect_get_attached_clients()
116            .with(always(), eq("session"))
117            .times(1)
118            .returning(|_, _| {
119                Err(Error::RemoteError {
120                    code: 500,
121                    errno: 101,
122                    error: "Did not work!".to_owned(),
123                    message: "Did not work!".to_owned(),
124                    info: "Did not work!".to_owned(),
125                })
126            });
127
128        fxa.set_client(Arc::new(client));
129        assert!(fxa.attached_clients_cache.is_none());
130
131        let res = fxa.get_attached_clients();
132        assert!(res.is_err());
133        assert!(fxa.attached_clients_cache.is_none());
134    }
135}