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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use crate::{error::Error, token_cache::CacheableToken};
use std::time::SystemTime;

/// Represents a access token as returned by `OAuth2` servers.
///
/// * It is produced by all authentication flows.
/// * It authenticates certain operations, and must be refreshed once it has
///  reached its expiry date.
///
/// The type is tuned to be suitable for direct de-serialization from server
/// replies, as well as for serialization for later reuse. This is the reason
/// for the two fields dealing with expiry - once in relative in and once in
/// absolute terms.
#[derive(Clone, PartialEq, Eq, Debug, serde::Deserialize)]
pub struct Token {
    /// used when authenticating calls to oauth2 enabled services.
    pub access_token: String,
    /// used to refresh an expired access_token.
    pub refresh_token: String,
    /// The token type as string - usually 'Bearer'.
    pub token_type: String,
    /// access_token will expire after this amount of time.
    /// Prefer using expiry_date()
    pub expires_in: Option<i64>,
    /// timestamp is seconds since epoch indicating when the token will expire
    /// in absolute terms.
    pub expires_in_timestamp: Option<SystemTime>,
}

impl CacheableToken for Token {
    /// Returns true if we are expired.
    #[inline]
    fn has_expired(&self) -> bool {
        if self.access_token.is_empty() {
            return true;
        }

        let expiry = self.expires_in_timestamp.unwrap_or_else(SystemTime::now);

        expiry <= SystemTime::now()
    }
}

#[derive(Debug)]
pub enum RequestReason {
    /// An existing token has expired
    Expired,
    /// The requested scopes or audience have never been seen before
    ParametersChanged,
}

/// Either a valid token, or an HTTP request that can be used to acquire one
#[derive(Debug)]
pub enum TokenOrRequest {
    /// A valid token that can be supplied in an API request
    Token(Token),
    Request {
        /// The parts of an HTTP request that must be sent to acquire the token,
        /// in the client of your choice
        request: http::Request<Vec<u8>>,
        /// The reason we need to retrieve a new token
        reason: RequestReason,
        /// An opaque hash of the unique parameters for which the request was constructed
        scope_hash: u64,
    },
}

/// A `TokenProvider` has a single method to implement `get_token_with_subject`.
/// Implementations are free to perform caching or always return a `Request` in
/// the `TokenOrRequest`.
pub trait TokenProvider {
    /// Attempts to retrieve a token that can be used in an API request, if we
    /// haven't already retrieved a token for the specified scopes, or the token
    /// has expired, an HTTP request is returned that can be used to retrieve a
    /// token.
    ///
    /// Note that the scopes are not sorted or in any other way manipulated, so
    /// any modifications to them will require a new token to be requested.
    #[inline]
    fn get_token<'a, S, I>(&self, scopes: I) -> Result<TokenOrRequest, Error>
    where
        S: AsRef<str> + 'a,
        I: IntoIterator<Item = &'a S> + Clone,
    {
        self.get_token_with_subject::<S, I, String>(None, scopes)
    }

    /// Like [`TokenProvider::get_token`], but allows the JWT
    /// ["subject"](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields)
    /// to be passed in.
    fn get_token_with_subject<'a, S, I, T>(
        &self,
        subject: Option<T>,
        scopes: I,
    ) -> Result<TokenOrRequest, Error>
    where
        S: AsRef<str> + 'a,
        I: IntoIterator<Item = &'a S> + Clone,
        T: Into<String>;

    /// Once a response has been received for a token request, call this method
    /// to deserialize the token (and potentially store it in a local cache for
    /// reuse until it expires).
    fn parse_token_response<S>(
        &self,
        hash: u64,
        response: http::Response<S>,
    ) -> Result<Token, Error>
    where
        S: AsRef<[u8]>;
}

impl std::convert::TryInto<http::header::HeaderValue> for Token {
    type Error = crate::Error;

    fn try_into(self) -> Result<http::header::HeaderValue, crate::Error> {
        let auth_header_val = format!("{} {}", self.token_type, self.access_token);
        http::header::HeaderValue::from_str(&auth_header_val)
            .map_err(|e| crate::Error::from(http::Error::from(e)))
    }
}

#[derive(serde::Deserialize, Debug)]
struct TokenResponse {
    /// The actual token
    access_token: String,
    /// The token type, pretty much always Header
    token_type: String,
    /// The time until the token expires and a new one needs to be requested
    expires_in: i64,
}

impl From<TokenResponse> for Token {
    fn from(tr: TokenResponse) -> Self {
        Self {
            access_token: tr.access_token,
            token_type: tr.token_type,
            refresh_token: String::new(),
            expires_in: Some(tr.expires_in),
            expires_in_timestamp: std::time::SystemTime::now()
                .checked_add(std::time::Duration::from_secs(tr.expires_in as u64)),
        }
    }
}