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