fix(microsoft365): scope token cache, fail on unimplemented encryption, use config dir
- Include hash of (tenant_id, client_id, auth_flow) in token cache filename to prevent cross-account token reuse after config changes. - Fail fast with a clear error when token_cache_encrypted is true, since encryption is not yet implemented (was silently storing plaintext). - Use the config directory (parent of config.toml) instead of workspace_dir for token cache storage, keeping bearer tokens out of the project tree.
This commit is contained in:
parent
71b16357f3
commit
c7f341885b
@ -1,6 +1,8 @@
|
||||
use anyhow::Context;
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Cached OAuth2 token state persisted to disk between runs.
|
||||
@ -31,14 +33,30 @@ impl TokenCache {
|
||||
pub fn new(
|
||||
config: super::types::Microsoft365ResolvedConfig,
|
||||
zeroclaw_dir: &std::path::Path,
|
||||
) -> Self {
|
||||
let cache_path = zeroclaw_dir.join("ms365_token_cache.json");
|
||||
) -> anyhow::Result<Self> {
|
||||
if config.token_cache_encrypted {
|
||||
anyhow::bail!(
|
||||
"microsoft365: token_cache_encrypted is enabled but encryption is not yet \
|
||||
implemented; refusing to store tokens in plaintext. Set token_cache_encrypted \
|
||||
to false or wait for encryption support."
|
||||
);
|
||||
}
|
||||
|
||||
// Scope cache file to (tenant_id, client_id, auth_flow) so config
|
||||
// changes never reuse tokens from a different account/flow.
|
||||
let mut hasher = DefaultHasher::new();
|
||||
config.tenant_id.hash(&mut hasher);
|
||||
config.client_id.hash(&mut hasher);
|
||||
config.auth_flow.hash(&mut hasher);
|
||||
let fingerprint = format!("{:016x}", hasher.finish());
|
||||
|
||||
let cache_path = zeroclaw_dir.join(format!("ms365_token_cache_{fingerprint}.json"));
|
||||
let cached = Self::load_from_disk(&cache_path);
|
||||
Self {
|
||||
Ok(Self {
|
||||
inner: RwLock::new(cached),
|
||||
config,
|
||||
cache_path,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a valid access token, refreshing or re-authenticating as needed.
|
||||
|
||||
@ -33,16 +33,16 @@ impl Microsoft365Tool {
|
||||
config: types::Microsoft365ResolvedConfig,
|
||||
security: Arc<SecurityPolicy>,
|
||||
zeroclaw_dir: &std::path::Path,
|
||||
) -> Self {
|
||||
) -> anyhow::Result<Self> {
|
||||
let http_client =
|
||||
crate::config::build_runtime_proxy_client_with_timeouts("tool.microsoft365", 60, 10);
|
||||
let token_cache = Arc::new(auth::TokenCache::new(config.clone(), zeroclaw_dir));
|
||||
Self {
|
||||
let token_cache = Arc::new(auth::TokenCache::new(config.clone(), zeroclaw_dir)?);
|
||||
Ok(Self {
|
||||
config,
|
||||
security,
|
||||
token_cache,
|
||||
http_client,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_token(&self) -> anyhow::Result<String> {
|
||||
|
||||
@ -387,11 +387,16 @@ pub fn all_tools_with_runtime(
|
||||
.unwrap_or("me")
|
||||
.to_string(),
|
||||
};
|
||||
tool_arcs.push(Arc::new(Microsoft365Tool::new(
|
||||
resolved,
|
||||
security.clone(),
|
||||
workspace_dir,
|
||||
)));
|
||||
// Store token cache in the config directory (next to config.toml),
|
||||
// not the workspace directory, to keep bearer tokens out of the
|
||||
// project tree.
|
||||
let cache_dir = root_config.config_path.parent().unwrap_or(workspace_dir);
|
||||
match Microsoft365Tool::new(resolved, security.clone(), cache_dir) {
|
||||
Ok(tool) => tool_arcs.push(Arc::new(tool)),
|
||||
Err(e) => {
|
||||
tracing::error!("microsoft365: failed to initialize tool: {e}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"microsoft365: skipped registration because tenant_id or client_id is empty"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user