fix(gateway): restore auth role refresh and Rust checks

Refresh the resolved user role without bypassing owner group and key policies. Resolve Rust 1.95 Clippy failures and make the pending persistence bound test scheduler-independent.
This commit is contained in:
elky
2026-07-22 11:25:24 +08:00
parent cd2fbeeead
commit 713010fa0a
8 changed files with 109 additions and 92 deletions
+8 -17
View File
@@ -175,25 +175,16 @@ impl Drop for AuthContextOwnedInflightGuard {
impl AuthContextCache {
fn finish_inflight(&self, cache_key: &str, state: &Arc<AuthContextInflightState>) {
let removed = self
.inflight
.lock()
.map(|mut inflight| {
if inflight
.get(cache_key)
.is_some_and(|current| Arc::ptr_eq(current, state))
{
state.complete();
inflight.remove(cache_key).is_some()
} else {
false
}
})
.unwrap_or(false);
if !removed {
if let Ok(mut inflight) = self.inflight.lock() {
// A clear may already have detached and completed this flight.
// Keep drop idempotent so an old guard cannot affect its replacement.
return;
if inflight
.get(cache_key)
.is_some_and(|current| Arc::ptr_eq(current, state))
{
state.complete();
inflight.remove(cache_key);
}
}
}
+47 -14
View File
@@ -1787,7 +1787,7 @@ impl GatewayDataState {
now_unix_secs,
)));
};
let Some(_) = crate::request_diagnostics::observe_db_operation(
let Some(user) = crate::request_diagnostics::observe_db_operation(
"auth_user_policy",
self.database_pool_summary(),
repository.find_user_auth_by_id(&snapshot.user_id),
@@ -1799,6 +1799,7 @@ impl GatewayDataState {
now_unix_secs,
)));
};
snapshot.user_role = user.role;
let groups = self
.effective_user_groups_for_user(&snapshot.user_id)
.await?;
@@ -2455,7 +2456,7 @@ mod tests {
}
#[tokio::test]
async fn admin_non_standalone_snapshot_bypasses_group_and_key_policies() {
async fn admin_non_standalone_snapshot_applies_group_and_key_policies() {
let mut snapshot = sample_snapshot_with_role("key-admin", "admin-1", "admin")
.with_user_rate_limit(Some(120));
snapshot.api_key_allowed_providers = Some(vec!["anthropic".to_string()]);
@@ -2501,16 +2502,17 @@ mod tests {
.expect("snapshot should resolve")
.expect("snapshot should exist");
assert_eq!(resolved.effective_allowed_providers(), None);
assert_eq!(resolved.effective_allowed_api_formats(), None);
assert_eq!(resolved.effective_allowed_models(), None);
assert_eq!(resolved.user_rate_limit, None);
assert_eq!(resolved.api_key_rate_limit, None);
assert_eq!(resolved.api_key_concurrent_limit, None);
assert_eq!(resolved.user_role, "admin");
assert_eq!(resolved.effective_allowed_providers(), Some(&[][..]));
assert_eq!(resolved.effective_allowed_api_formats(), Some(&[][..]));
assert_eq!(resolved.effective_allowed_models(), Some(&[][..]));
assert_eq!(resolved.user_rate_limit, Some(1));
assert_eq!(resolved.api_key_rate_limit, Some(5));
assert_eq!(resolved.api_key_concurrent_limit, Some(1));
}
#[tokio::test]
async fn current_admin_role_bypasses_stored_user_and_key_policies() {
async fn current_admin_role_refreshes_without_bypassing_key_policies() {
let mut snapshot = sample_snapshot("key-admin", "admin-1");
snapshot.api_key_allowed_providers = Some(vec!["anthropic".to_string()]);
snapshot.api_key_allowed_api_formats = Some(vec!["anthropic:messages".to_string()]);
@@ -2533,12 +2535,43 @@ mod tests {
.expect("snapshot should exist");
assert_eq!(resolved.user_role, "admin");
assert_eq!(resolved.effective_allowed_providers(), None);
assert_eq!(resolved.effective_allowed_api_formats(), None);
assert_eq!(resolved.effective_allowed_models(), None);
assert_eq!(
resolved.effective_allowed_providers(),
Some(&["anthropic".to_string()][..])
);
assert_eq!(
resolved.effective_allowed_api_formats(),
Some(&["anthropic:messages".to_string()][..])
);
assert_eq!(
resolved.effective_allowed_models(),
Some(&["claude-sonnet-4-5".to_string()][..])
);
assert_eq!(resolved.user_rate_limit, None);
assert_eq!(resolved.api_key_rate_limit, None);
assert_eq!(resolved.api_key_concurrent_limit, None);
assert_eq!(resolved.api_key_rate_limit, Some(60));
assert_eq!(resolved.api_key_concurrent_limit, Some(5));
}
#[tokio::test]
async fn current_user_role_replaces_stored_admin_role() {
let snapshot = sample_snapshot_with_role("key-user", "user-1", "admin");
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
Some("hash-user".to_string()),
snapshot,
)]));
let user_repository = Arc::new(InMemoryUserReadRepository::seed_auth_users(vec![
sample_auth_user("user-1", "user"),
]));
let state = GatewayDataState::with_auth_api_key_reader_for_tests(auth_repository)
.with_user_reader(user_repository);
let resolved = state
.read_auth_api_key_snapshot_by_key_hash("hash-user", 100)
.await
.expect("snapshot should resolve")
.expect("snapshot should exist");
assert_eq!(resolved.user_role, "user");
}
#[tokio::test]
@@ -474,7 +474,7 @@ where
self.state,
self.trace_id,
self.decision,
&plan,
plan,
report_context.as_ref(),
)
.await?
@@ -376,7 +376,7 @@ async fn sync_gemini_cli_credits_from_report(
.as_ref()
.and_then(Value::as_object)
.cloned()
.unwrap_or_else(serde_json::Map::new);
.unwrap_or_default();
gemini_cli_bucket.insert("credits".to_string(), credits.clone());
gemini_cli_bucket.insert("updated_at".to_string(), json!(now_unix_secs));
@@ -889,7 +889,7 @@ async fn sync_codex_quota_from_response_headers(
let current_codex = expected_namespace_value
.clone()
.and_then(|value| value.as_object().cloned())
.unwrap_or_else(serde_json::Map::new);
.unwrap_or_default();
let current_codex = Value::Object(current_codex);
let Some(current_fingerprint) = fingerprint_codex_payload(&current_codex) else {
set_cached_codex_quota_fingerprint(&key_id, incoming_fingerprint.clone(), now);
@@ -925,7 +925,7 @@ async fn sync_codex_quota_from_response_headers(
// Response headers describe an authoritative quota snapshot. A CAS
// conflict means a newer snapshot/delta won, so avoid replaying stale
// data over it.
return Ok(false);
Ok(false)
}
#[cfg(test)]