service_authenticator/
authenticator.rs

1//! Module contianing the core functionality for OAuth2 Authentication.
2use crate::error::Error;
3use crate::service_account::{ServiceAccountFlow, ServiceAccountFlowOpts, ServiceAccountKey};
4use crate::storage::{self, Storage};
5use crate::types::AccessToken;
6use actix_web::client as awc;
7use futures::lock::Mutex;
8use std::fmt;
9use std::io;
10use std::path::PathBuf;
11/// Authenticator is responsible for fetching tokens, handling refreshing tokens,
12/// and optionally persisting tokens to disk.
13pub struct Authenticator {
14  /// client field is public so that it may be used for sending requests
15  /// with the authorization header built from the received token
16  pub client: awc::Client,
17  storage: Storage,
18  auth_flow: ServiceAccountFlow,
19}
20
21impl Authenticator {
22  /// Return the current token for the provided scopes.
23  pub async fn token<'a, T>(
24    &'a self,
25    scopes: &'a [T],
26  ) -> Result<AccessToken, Error>
27  where
28    T: AsRef<str>,
29  {
30    self.find_token(scopes, /* force_refresh = */ false).await
31  }
32  /// returns value for the `Authorization` header (Bearer <token value>)
33  pub async fn header<'a, T>(
34    self: &Self,
35    scopes: &'a [T],
36  ) -> Result<String, Error>
37  where
38    T: AsRef<str>,
39  {
40    let tok = self.token(scopes).await?;
41    Ok(format!("Bearer {}", tok.as_str()))
42  }
43  /// Return a token for the provided scopes, but don't reuse cached tokens. Instead,
44  /// always fetch a new token from the OAuth server.
45  pub async fn force_refreshed_token<'a, T>(
46    &'a self,
47    scopes: &'a [T],
48  ) -> Result<AccessToken, Error>
49  where
50    T: AsRef<str>,
51  {
52    self.find_token(scopes, /* force_refresh = */ true).await
53  }
54  /// Return a cached token or fetch a new one from the server.
55  async fn find_token<'a, T>(
56    &'a self,
57    scopes: &'a [T],
58    force_refresh: bool,
59  ) -> Result<AccessToken, Error>
60  where
61    T: AsRef<str>,
62  {
63    log::debug!(
64      "access token requested for scopes: {}",
65      DisplayScopes(scopes)
66    );
67    let hashed_scopes = storage::ScopeSet::from(scopes);
68    match self.storage.get(hashed_scopes).await {
69      Some(t) if !t.is_expired() && !force_refresh => {
70        // unexpired token found
71        log::debug!("found valid token in cache: {:?}", t);
72        Ok(t.into())
73      }
74      _ => {
75        // no token in the cache or the token returned can't be refreshed.
76        let token_info = self.auth_flow.token(&self.client, scopes).await?;
77        self.storage.set(hashed_scopes, token_info.clone()).await?;
78        Ok(token_info.into())
79      }
80    }
81  }
82}
83struct DisplayScopes<'a, T>(&'a [T]);
84impl<'a, T> fmt::Display for DisplayScopes<'a, T>
85where
86  T: AsRef<str>,
87{
88  fn fmt(
89    &self,
90    f: &mut fmt::Formatter,
91  ) -> fmt::Result {
92    f.write_str("[")?;
93    let mut iter = self.0.iter();
94    if let Some(first) = iter.next() {
95      f.write_str(first.as_ref())?;
96      for scope in iter {
97        f.write_str(", ")?;
98        f.write_str(scope.as_ref())?;
99      }
100    }
101    f.write_str("]")
102  }
103}
104/// Configure an Authenticator using the builder pattern.
105pub struct AuthenticatorBuilder {
106  client_builder: awc::ClientBuilder,
107  storage_type: StorageType,
108  auth_flow_opts: ServiceAccountFlowOpts,
109}
110
111impl AuthenticatorBuilder {
112  async fn common_build(
113    client_builder: awc::ClientBuilder,
114    storage_type: StorageType,
115    auth_flow_opts: ServiceAccountFlowOpts,
116  ) -> io::Result<Authenticator> {
117    let client = client_builder.finish();
118    let storage = match storage_type {
119      StorageType::Memory => Storage::Memory {
120        tokens: Mutex::new(storage::JSONTokens::new()),
121      },
122      StorageType::Disk(path) => Storage::Disk(storage::DiskStorage::new(path).await?),
123    };
124    let auth_flow = ServiceAccountFlow::new(auth_flow_opts)?;
125    Ok(Authenticator {
126      client,
127      storage,
128      auth_flow,
129    })
130  }
131  /// Use the provided client builder.
132  pub fn client(
133    self,
134    client_builder: awc::ClientBuilder,
135  ) -> AuthenticatorBuilder {
136    AuthenticatorBuilder {
137      client_builder: client_builder,
138      storage_type: self.storage_type,
139      auth_flow_opts: self.auth_flow_opts,
140    }
141  }
142  /// Persist tokens to disk in the provided filename.
143  pub fn persist_tokens_to_disk<P: Into<PathBuf>>(
144    self,
145    path: P,
146  ) -> AuthenticatorBuilder {
147    AuthenticatorBuilder {
148      storage_type: StorageType::Disk(path.into()),
149      ..self
150    }
151  }
152  /// uses provided ServiceAccountKey and subject
153  pub fn with_service_key(
154    key: ServiceAccountKey,
155    subject: &str,
156  ) -> AuthenticatorBuilder {
157    let auth_flow_opts = ServiceAccountFlowOpts {
158      key,
159      subject: Some(subject.to_string()),
160    };
161    AuthenticatorBuilder {
162      client_builder: awc::ClientBuilder::new(),
163      storage_type: StorageType::Memory,
164      auth_flow_opts,
165    }
166  }
167}
168/// ## Methods available when building a service account authenticator.
169/// ```
170/// # async fn foo() {
171/// # let service_account_key = yup_oauth2::read_service_account_key("/tmp/foo").await.unwrap();
172///     let authenticator = yup_oauth2::ServiceAccountAuthenticator::builder(
173///         service_account_key,
174///     )
175///     .subject("mysubject")
176///     .build()
177///     .await
178///     .expect("failed to create authenticator");
179/// # }
180/// ```
181impl AuthenticatorBuilder {
182  /// Use the provided subject.
183  pub fn subject(
184    self,
185    subject: impl Into<String>,
186  ) -> Self {
187    AuthenticatorBuilder {
188      auth_flow_opts: ServiceAccountFlowOpts {
189        subject: Some(subject.into()),
190        ..self.auth_flow_opts
191      },
192      ..self
193    }
194  }
195
196  /// Create the authenticator.
197  pub async fn build(self) -> io::Result<Authenticator> {
198    Self::common_build(self.client_builder, self.storage_type, self.auth_flow_opts).await
199  }
200}
201enum StorageType {
202  Memory,
203  Disk(PathBuf),
204}