bitwarden_auth/token_management/
secrets_manager_token_handler.rs1use std::sync::{Arc, RwLock};
4
5use bitwarden_core::{
6 NotAuthenticatedError, OrganizationId,
7 auth::{TokenHandler, login::LoginError},
8 client::login_method::{LoginMethod, ServiceAccountLoginMethod},
9 key_management::KeyIds,
10};
11use bitwarden_crypto::KeyStore;
12use chrono::Utc;
13
14use super::middleware::{MiddlewareExt, MiddlewareWrapper};
15use crate::token_management::middleware::TOKEN_RENEW_MARGIN_SECONDS;
16
17#[derive(Clone, Default)]
19pub struct SecretsManagerTokenHandler {
20 inner: Arc<RwLock<SecretsManagerTokenHandlerInner>>,
21}
22
23#[derive(Clone, Default)]
24struct SecretsManagerTokenHandlerInner {
25 access_token: Option<String>,
26 expires_on: Option<i64>,
27
28 login_method: Option<Arc<RwLock<Option<Arc<LoginMethod>>>>>,
31 identity_config: Option<bitwarden_api_api::Configuration>,
32 key_store: Option<KeyStore<KeyIds>>,
33}
34
35impl TokenHandler for SecretsManagerTokenHandler {
36 fn initialize_middleware(
37 &self,
38 login_method: Arc<RwLock<Option<Arc<LoginMethod>>>>,
39 identity_config: bitwarden_api_api::Configuration,
40 key_store: KeyStore<KeyIds>,
41 ) -> Arc<dyn reqwest_middleware::Middleware> {
42 {
43 let mut inner = self.inner.write().expect("RwLock is not poisoned");
44 inner.login_method = Some(login_method);
45 inner.identity_config = Some(identity_config);
46 inner.key_store = Some(key_store);
47 }
48 Arc::new(MiddlewareWrapper(self.clone()))
49 }
50
51 fn set_tokens(&self, access_token: String, _refresh_token: Option<String>, expires_in: u64) {
52 let mut inner = self.inner.write().expect("RwLock is not poisoned");
53 inner.access_token = Some(access_token);
54 inner.expires_on = Some(Utc::now().timestamp() + expires_in as i64);
55 }
56}
57
58impl SecretsManagerTokenHandler {
59 pub fn get_access_token_organization(&self) -> Option<OrganizationId> {
61 let guard = self.inner.read().ok()?;
62 let login_method = guard.login_method.as_ref()?.read().ok()?;
63
64 match login_method.as_ref()?.as_ref() {
65 LoginMethod::User(_) => None,
66 LoginMethod::ServiceAccount(ServiceAccountLoginMethod::AccessToken {
67 organization_id,
68 ..
69 }) => Some(*organization_id),
70 }
71 }
72}
73
74#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
75#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
76impl MiddlewareExt for SecretsManagerTokenHandler {
77 async fn get_token(&self) -> Result<Option<String>, LoginError> {
78 let inner = self.inner.read().expect("RwLock is not poisoned").clone();
84
85 if let Some(expires) = inner.expires_on
87 && Utc::now().timestamp() < expires - TOKEN_RENEW_MARGIN_SECONDS
88 {
89 return Ok(inner.access_token.clone());
90 }
91
92 let login_method = inner.login_method.ok_or(NotAuthenticatedError)?;
95 let identity_config = inner.identity_config.ok_or(NotAuthenticatedError)?;
96 let key_store = inner.key_store.ok_or(NotAuthenticatedError)?;
97
98 let login_method = login_method
99 .read()
100 .expect("RwLock is not poisoned")
101 .clone()
102 .ok_or(NotAuthenticatedError)?;
103
104 let LoginMethod::ServiceAccount(service_account_login_method) = login_method.as_ref()
105 else {
106 return Err(NotAuthenticatedError.into());
107 };
108
109 let (access_token, refresh_token, expires_in) =
110 bitwarden_core::auth::renew::renew_sm_token_sdk_managed(
111 service_account_login_method,
112 identity_config,
113 key_store,
114 )
115 .await?;
116
117 self.set_tokens(access_token.clone(), refresh_token, expires_in);
118 Ok(Some(access_token))
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use std::{
125 str::FromStr,
126 sync::{Arc, RwLock},
127 };
128
129 use bitwarden_core::{
130 auth::{AccessToken, TokenHandler},
131 client::login_method::{LoginMethod, ServiceAccountLoginMethod},
132 key_management::KeyIds,
133 };
134 use bitwarden_crypto::KeyStore;
135 use wiremock::MockServer;
136
137 use super::*;
138 use crate::token_management::test_utils::*;
139
140 fn service_account_login_method() -> Arc<RwLock<Option<Arc<LoginMethod>>>> {
141 let access_token = AccessToken::from_str(
142 "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ==",
143 )
144 .unwrap();
145
146 Arc::new(RwLock::new(Some(Arc::new(LoginMethod::ServiceAccount(
147 ServiceAccountLoginMethod::AccessToken {
148 access_token,
149 organization_id: "00000000-0000-0000-0000-000000000001".parse().unwrap(),
150 state_file: None,
151 },
152 )))))
153 }
154
155 #[tokio::test]
156 async fn attaches_existing_token_when_not_expired() {
157 let app_server = start_app_server().await;
158 let identity_server = MockServer::start().await;
159
160 let handler = SecretsManagerTokenHandler::default();
161 handler.set_tokens("original-token".to_string(), None, 3600);
162
163 let client = build_client(handler.initialize_middleware(
164 service_account_login_method(),
165 identity_config(&identity_server.uri()),
166 KeyStore::<KeyIds>::default(),
167 ));
168
169 let auth = send_auth_request(&client, &app_server).await;
170 assert_eq!(auth.as_deref(), Some("Bearer original-token"));
171 assert_eq!(identity_server.received_requests().await.unwrap().len(), 0);
172 assert_eq!(app_server.received_requests().await.unwrap().len(), 1);
173 }
174
175 #[tokio::test]
176 async fn renews_expired_token() {
177 let app_server = start_app_server().await;
178 let identity_server = start_renewal_server("renewed-token").await;
179
180 let handler = SecretsManagerTokenHandler::default();
181 handler.set_tokens("expired-token".to_string(), None, 0);
183
184 let client = build_client(handler.initialize_middleware(
185 service_account_login_method(),
186 identity_config(&identity_server.uri()),
187 KeyStore::<KeyIds>::default(),
188 ));
189
190 let auth = send_auth_request(&client, &app_server).await;
191 assert_eq!(auth.as_deref(), Some("Bearer renewed-token"));
192 assert_eq!(identity_server.received_requests().await.unwrap().len(), 1);
193 assert_eq!(app_server.received_requests().await.unwrap().len(), 1);
194 }
195}