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
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)))
    }
}