1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

pub use crate::http_client::GetAttachedClientResponse as AttachedClient;
use crate::{error::*, util, CachedResponse, FirefoxAccount};

// An attached clients response is considered fresh for `ATTACHED_CLIENTS_FRESHNESS_THRESHOLD` ms.
const ATTACHED_CLIENTS_FRESHNESS_THRESHOLD: u64 = 60_000; // 1 minute

impl FirefoxAccount {
    /// Fetches the list of attached clients connected to the current account.
    pub fn get_attached_clients(&mut self) -> Result<Vec<AttachedClient>> {
        if let Some(a) = &self.attached_clients_cache {
            if util::now() < a.cached_at + ATTACHED_CLIENTS_FRESHNESS_THRESHOLD {
                return Ok(a.response.clone());
            }
        }
        let session_token = self.get_session_token()?;
        let response = self
            .client
            .attached_clients(&self.state.config, &session_token)?;

        self.attached_clients_cache = Some(CachedResponse {
            response: response.clone(),
            cached_at: util::now(),
            etag: "".into(),
        });

        Ok(response)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        config::Config,
        http_client::{DeviceType, FxAClientMock},
    };
    use std::sync::Arc;

    #[test]
    fn test_get_attached_clients() {
        let config = Config::stable_dev("12345678", "https://foo.bar");
        let mut fxa = FirefoxAccount::with_config(config);
        fxa.set_session_token("session");

        let mut client = FxAClientMock::new();
        client
            .expect_attached_clients(mockiato::Argument::any, |arg| arg.partial_eq("session"))
            .times(1)
            .returns_once(Ok(vec![AttachedClient {
                client_id: Some("12345678".into()),
                session_token_id: None,
                refresh_token_id: None,
                device_id: None,
                device_type: Some(DeviceType::Desktop),
                is_current_session: true,
                name: None,
                created_time: None,
                last_access_time: None,
                scope: None,
                user_agent: "attachedClientsUserAgent".into(),
                os: None,
            }]));

        fxa.set_client(Arc::new(client));
        assert!(fxa.attached_clients_cache.is_none());

        let res = fxa.get_attached_clients();

        assert!(res.is_ok());
        assert!(fxa.attached_clients_cache.is_some());

        let cached_attached_clients_res = fxa.attached_clients_cache.unwrap();
        assert!(!cached_attached_clients_res.response.is_empty());
        assert!(cached_attached_clients_res.cached_at > 0);

        let cached_attached_clients = &cached_attached_clients_res.response[0];
        assert_eq!(
            cached_attached_clients.clone().client_id.unwrap(),
            "12345678".to_string()
        );
    }

    #[test]
    fn test_get_attached_clients_network_errors() {
        let config = Config::stable_dev("12345678", "https://foo.bar");
        let mut fxa = FirefoxAccount::with_config(config);
        fxa.set_session_token("session");

        let mut client = FxAClientMock::new();
        client
            .expect_attached_clients(mockiato::Argument::any, |arg| arg.partial_eq("session"))
            .times(1)
            .returns_once(Err(ErrorKind::RemoteError {
                code: 500,
                errno: 101,
                error: "Did not work!".to_owned(),
                message: "Did not work!".to_owned(),
                info: "Did not work!".to_owned(),
            }
            .into()));

        fxa.set_client(Arc::new(client));
        assert!(fxa.attached_clients_cache.is_none());

        let res = fxa.get_attached_clients();
        assert!(res.is_err());
        assert!(fxa.attached_clients_cache.is_none());
    }
}