mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
Redesign sensitive info protection settings
This commit is contained in:
@@ -58,6 +58,67 @@ fn auth_snapshot() -> StoredAuthApiKeySnapshot {
|
||||
.expect("auth snapshot should build")
|
||||
}
|
||||
|
||||
fn auth_export_record(
|
||||
snapshot: &StoredAuthApiKeySnapshot,
|
||||
key_hash: String,
|
||||
feature_settings: Option<serde_json::Value>,
|
||||
) -> aether_data::repository::auth::StoredAuthApiKeyExportRecord {
|
||||
aether_data::repository::auth::StoredAuthApiKeyExportRecord::new(
|
||||
snapshot.user_id.clone(),
|
||||
snapshot.api_key_id.clone(),
|
||||
key_hash,
|
||||
None,
|
||||
snapshot.api_key_name.clone(),
|
||||
snapshot
|
||||
.api_key_allowed_providers
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot
|
||||
.api_key_allowed_api_formats
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot
|
||||
.api_key_allowed_models
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot.api_key_rate_limit,
|
||||
snapshot.api_key_concurrent_limit,
|
||||
None,
|
||||
snapshot.api_key_is_active,
|
||||
snapshot
|
||||
.api_key_expires_at_unix_secs
|
||||
.map(|value| value as i64),
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
0.0,
|
||||
snapshot.api_key_is_standalone,
|
||||
)
|
||||
.expect("auth api key export record should build")
|
||||
.with_feature_settings(feature_settings)
|
||||
}
|
||||
|
||||
fn auth_repository_with_redaction_feature_settings() -> Arc<InMemoryAuthApiKeySnapshotRepository> {
|
||||
let snapshot = auth_snapshot();
|
||||
let key_hash = hash_api_key("sk-client-ai-execute-stream-pii-redaction");
|
||||
Arc::new(
|
||||
InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(key_hash.clone()),
|
||||
snapshot.clone(),
|
||||
)])
|
||||
.with_export_records(vec![auth_export_record(
|
||||
&snapshot,
|
||||
key_hash,
|
||||
Some(json!({
|
||||
"chat_pii_redaction": {
|
||||
"enabled": true,
|
||||
"inject_model_instruction": true,
|
||||
}
|
||||
})),
|
||||
)]),
|
||||
)
|
||||
}
|
||||
|
||||
fn candidate_row() -> StoredMinimalCandidateSelectionRow {
|
||||
StoredMinimalCandidateSelectionRow {
|
||||
provider_id: "provider-ai-execute-stream-pii-redaction".to_string(),
|
||||
@@ -233,10 +294,7 @@ async fn ai_execute_stream_pii_redaction_round_trip() {
|
||||
}),
|
||||
);
|
||||
let (provider_url, provider_handle) = start_server(provider_app).await;
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key("sk-client-ai-execute-stream-pii-redaction")),
|
||||
auth_snapshot(),
|
||||
)]));
|
||||
let auth_repository = auth_repository_with_redaction_feature_settings();
|
||||
let candidate_selection_repository =
|
||||
Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
|
||||
candidate_row(),
|
||||
@@ -257,21 +315,20 @@ async fn ai_execute_stream_pii_redaction_round_trip() {
|
||||
.with_system_config_values_for_tests(vec![
|
||||
("module.chat_pii_redaction.enabled".to_string(), json!(true)),
|
||||
(
|
||||
"module.chat_pii_redaction.provider_scope".to_string(),
|
||||
json!("selected_providers"),
|
||||
),
|
||||
(
|
||||
"module.chat_pii_redaction.entities".to_string(),
|
||||
json!(["email"]),
|
||||
"module.chat_pii_redaction.rules".to_string(),
|
||||
json!([{
|
||||
"id": "email",
|
||||
"name": "邮箱",
|
||||
"pattern": r"(?i)[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\.[A-Z]{2,63}",
|
||||
"enabled": true,
|
||||
"features": {"validator": "email"},
|
||||
"system": true
|
||||
}]),
|
||||
),
|
||||
(
|
||||
"module.chat_pii_redaction.cache_ttl_seconds".to_string(),
|
||||
json!(300),
|
||||
),
|
||||
(
|
||||
"module.chat_pii_redaction.inject_model_instruction".to_string(),
|
||||
json!(true),
|
||||
),
|
||||
]);
|
||||
let gateway_state = AppState::new()
|
||||
.expect("gateway state should build")
|
||||
|
||||
@@ -52,6 +52,68 @@ async fn proxy_pii_redaction_local_openai_chat_runtime_masks_headers_and_restore
|
||||
.expect("auth snapshot should build")
|
||||
}
|
||||
|
||||
fn auth_export_record(
|
||||
snapshot: &StoredAuthApiKeySnapshot,
|
||||
key_hash: String,
|
||||
feature_settings: Option<serde_json::Value>,
|
||||
) -> aether_data::repository::auth::StoredAuthApiKeyExportRecord {
|
||||
aether_data::repository::auth::StoredAuthApiKeyExportRecord::new(
|
||||
snapshot.user_id.clone(),
|
||||
snapshot.api_key_id.clone(),
|
||||
key_hash,
|
||||
None,
|
||||
snapshot.api_key_name.clone(),
|
||||
snapshot
|
||||
.api_key_allowed_providers
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot
|
||||
.api_key_allowed_api_formats
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot
|
||||
.api_key_allowed_models
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot.api_key_rate_limit,
|
||||
snapshot.api_key_concurrent_limit,
|
||||
None,
|
||||
snapshot.api_key_is_active,
|
||||
snapshot
|
||||
.api_key_expires_at_unix_secs
|
||||
.map(|value| value as i64),
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
0.0,
|
||||
snapshot.api_key_is_standalone,
|
||||
)
|
||||
.expect("auth api key export record should build")
|
||||
.with_feature_settings(feature_settings)
|
||||
}
|
||||
|
||||
fn auth_repository_with_redaction_feature_settings() -> Arc<InMemoryAuthApiKeySnapshotRepository>
|
||||
{
|
||||
let snapshot = auth_snapshot();
|
||||
let key_hash = hash_api_key("sk-client-redaction");
|
||||
Arc::new(
|
||||
InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(key_hash.clone()),
|
||||
snapshot.clone(),
|
||||
)])
|
||||
.with_export_records(vec![auth_export_record(
|
||||
&snapshot,
|
||||
key_hash,
|
||||
Some(json!({
|
||||
"chat_pii_redaction": {
|
||||
"enabled": true,
|
||||
"inject_model_instruction": true,
|
||||
}
|
||||
})),
|
||||
)]),
|
||||
)
|
||||
}
|
||||
|
||||
fn candidate_row() -> StoredMinimalCandidateSelectionRow {
|
||||
StoredMinimalCandidateSelectionRow {
|
||||
provider_id: "provider-redaction-1".to_string(),
|
||||
@@ -212,10 +274,7 @@ async fn proxy_pii_redaction_local_openai_chat_runtime_masks_headers_and_restore
|
||||
}),
|
||||
);
|
||||
let (provider_url, provider_handle) = start_server(provider_app).await;
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key("sk-client-redaction")),
|
||||
auth_snapshot(),
|
||||
)]));
|
||||
let auth_repository = auth_repository_with_redaction_feature_settings();
|
||||
let candidate_selection_repository =
|
||||
Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
|
||||
candidate_row(),
|
||||
@@ -236,21 +295,20 @@ async fn proxy_pii_redaction_local_openai_chat_runtime_masks_headers_and_restore
|
||||
.with_system_config_values_for_tests(vec![
|
||||
("module.chat_pii_redaction.enabled".to_string(), json!(true)),
|
||||
(
|
||||
"module.chat_pii_redaction.provider_scope".to_string(),
|
||||
json!("selected_providers"),
|
||||
),
|
||||
(
|
||||
"module.chat_pii_redaction.entities".to_string(),
|
||||
json!(["email"]),
|
||||
"module.chat_pii_redaction.rules".to_string(),
|
||||
json!([{
|
||||
"id": "email",
|
||||
"name": "邮箱",
|
||||
"pattern": r"(?i)[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\.[A-Z]{2,63}",
|
||||
"enabled": true,
|
||||
"features": {"validator": "email"},
|
||||
"system": true
|
||||
}]),
|
||||
),
|
||||
(
|
||||
"module.chat_pii_redaction.cache_ttl_seconds".to_string(),
|
||||
json!(300),
|
||||
),
|
||||
(
|
||||
"module.chat_pii_redaction.inject_model_instruction".to_string(),
|
||||
json!(true),
|
||||
),
|
||||
]);
|
||||
let gateway_state = AppState::new()
|
||||
.expect("gateway state should build")
|
||||
|
||||
@@ -41,6 +41,46 @@ fn auth_snapshot(api_key_id: &str, user_id: &str) -> StoredAuthApiKeySnapshot {
|
||||
.expect("auth snapshot should build")
|
||||
}
|
||||
|
||||
fn auth_export_record(
|
||||
snapshot: &StoredAuthApiKeySnapshot,
|
||||
key_hash: String,
|
||||
feature_settings: Option<serde_json::Value>,
|
||||
) -> aether_data::repository::auth::StoredAuthApiKeyExportRecord {
|
||||
aether_data::repository::auth::StoredAuthApiKeyExportRecord::new(
|
||||
snapshot.user_id.clone(),
|
||||
snapshot.api_key_id.clone(),
|
||||
key_hash,
|
||||
None,
|
||||
snapshot.api_key_name.clone(),
|
||||
snapshot
|
||||
.api_key_allowed_providers
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot
|
||||
.api_key_allowed_api_formats
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot
|
||||
.api_key_allowed_models
|
||||
.as_ref()
|
||||
.map(|value| serde_json::json!(value)),
|
||||
snapshot.api_key_rate_limit,
|
||||
snapshot.api_key_concurrent_limit,
|
||||
None,
|
||||
snapshot.api_key_is_active,
|
||||
snapshot
|
||||
.api_key_expires_at_unix_secs
|
||||
.map(|value| value as i64),
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
0.0,
|
||||
snapshot.api_key_is_standalone,
|
||||
)
|
||||
.expect("auth api key export record should build")
|
||||
.with_feature_settings(feature_settings)
|
||||
}
|
||||
|
||||
fn candidate_row(test_id: &str) -> StoredMinimalCandidateSelectionRow {
|
||||
StoredMinimalCandidateSelectionRow {
|
||||
provider_id: format!("provider-{test_id}"),
|
||||
@@ -141,37 +181,90 @@ fn key(test_id: &str) -> StoredProviderCatalogKey {
|
||||
}
|
||||
|
||||
fn redaction_config(module_enabled: bool) -> Vec<(String, serde_json::Value)> {
|
||||
redaction_config_with_entities(
|
||||
module_enabled,
|
||||
json!(["email", "access_token", "secret_key"]),
|
||||
)
|
||||
redaction_config_with_rules(module_enabled, redaction_test_rules())
|
||||
}
|
||||
|
||||
fn redaction_config_with_entities(
|
||||
fn redaction_config_with_rules(
|
||||
module_enabled: bool,
|
||||
entities: serde_json::Value,
|
||||
rules: serde_json::Value,
|
||||
) -> Vec<(String, serde_json::Value)> {
|
||||
vec![
|
||||
(
|
||||
"module.chat_pii_redaction.enabled".to_string(),
|
||||
json!(module_enabled),
|
||||
),
|
||||
(
|
||||
"module.chat_pii_redaction.provider_scope".to_string(),
|
||||
json!("selected_providers"),
|
||||
),
|
||||
("module.chat_pii_redaction.entities".to_string(), entities),
|
||||
("module.chat_pii_redaction.rules".to_string(), rules),
|
||||
(
|
||||
"module.chat_pii_redaction.cache_ttl_seconds".to_string(),
|
||||
json!(300),
|
||||
),
|
||||
(
|
||||
"module.chat_pii_redaction.inject_model_instruction".to_string(),
|
||||
json!(true),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn redaction_test_rules() -> serde_json::Value {
|
||||
json!([
|
||||
{
|
||||
"id": "email",
|
||||
"name": "邮箱",
|
||||
"pattern": r"(?i)[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\.[A-Z]{2,63}",
|
||||
"enabled": true,
|
||||
"features": {"validator": "email"},
|
||||
"system": true
|
||||
},
|
||||
{
|
||||
"id": "access_token",
|
||||
"name": "Access Token",
|
||||
"pattern": r#"(?i)\baccess[_-]?token\s*[:=]\s*["']?[A-Za-z0-9._~+/=-]{20,}"#,
|
||||
"enabled": true,
|
||||
"features": {"validator": "access_token"},
|
||||
"system": true
|
||||
},
|
||||
{
|
||||
"id": "secret_key",
|
||||
"name": "Secret Key",
|
||||
"pattern": r#"(?i)\bsecret[_-]?key\s*[:=]\s*["']?[A-Za-z0-9._~+/=-]{20,}"#,
|
||||
"enabled": true,
|
||||
"features": {"validator": "secret_key"},
|
||||
"system": true
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
fn chat_pii_redaction_feature_settings(
|
||||
enabled: bool,
|
||||
inject_model_instruction: bool,
|
||||
) -> serde_json::Value {
|
||||
json!({
|
||||
"chat_pii_redaction": {
|
||||
"enabled": enabled,
|
||||
"inject_model_instruction": inject_model_instruction,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn auth_repository_with_redaction_feature_settings(
|
||||
test_id: &str,
|
||||
feature_enabled: bool,
|
||||
inject_model_instruction: bool,
|
||||
) -> Arc<InMemoryAuthApiKeySnapshotRepository> {
|
||||
let snapshot = auth_snapshot(&format!("api-key-{test_id}"), &format!("user-{test_id}"));
|
||||
let key_hash = hash_api_key(&format!("sk-client-{test_id}"));
|
||||
Arc::new(
|
||||
InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(key_hash.clone()),
|
||||
snapshot.clone(),
|
||||
)])
|
||||
.with_export_records(vec![auth_export_record(
|
||||
&snapshot,
|
||||
key_hash,
|
||||
Some(chat_pii_redaction_feature_settings(
|
||||
feature_enabled,
|
||||
inject_model_instruction,
|
||||
)),
|
||||
)]),
|
||||
)
|
||||
}
|
||||
|
||||
fn collect_sentinels(text: &str, kind: &str) -> Vec<String> {
|
||||
let prefix = format!("<AETHER:{kind}:");
|
||||
let mut sentinels = Vec::new();
|
||||
@@ -191,13 +284,13 @@ fn collect_sentinels(text: &str, kind: &str) -> Vec<String> {
|
||||
async fn run_sync_redaction_case(
|
||||
test_id: &str,
|
||||
module_enabled: bool,
|
||||
provider_enabled: bool,
|
||||
feature_enabled: bool,
|
||||
provider_response: &'static str,
|
||||
request_body: serde_json::Value,
|
||||
) -> (serde_json::Value, SeenProviderRequest) {
|
||||
run_sync_redaction_case_with_system_config(
|
||||
test_id,
|
||||
provider_enabled,
|
||||
feature_enabled,
|
||||
provider_response,
|
||||
request_body,
|
||||
redaction_config(module_enabled),
|
||||
@@ -207,7 +300,7 @@ async fn run_sync_redaction_case(
|
||||
|
||||
async fn run_sync_redaction_case_with_system_config(
|
||||
test_id: &str,
|
||||
provider_enabled: bool,
|
||||
feature_enabled: bool,
|
||||
provider_response: &'static str,
|
||||
request_body: serde_json::Value,
|
||||
system_config: Vec<(String, serde_json::Value)>,
|
||||
@@ -279,17 +372,15 @@ async fn run_sync_redaction_case_with_system_config(
|
||||
}),
|
||||
);
|
||||
let (provider_url, provider_handle) = start_server(provider_app).await;
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key(&format!("sk-client-{test_id}"))),
|
||||
auth_snapshot(&format!("api-key-{test_id}"), &format!("user-{test_id}")),
|
||||
)]));
|
||||
let auth_repository =
|
||||
auth_repository_with_redaction_feature_settings(test_id, feature_enabled, true);
|
||||
let candidate_selection_repository =
|
||||
Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
|
||||
candidate_row(test_id),
|
||||
]));
|
||||
let request_candidate_repository = Arc::new(InMemoryRequestCandidateRepository::default());
|
||||
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
|
||||
vec![provider(test_id, provider_enabled)],
|
||||
vec![provider(test_id, true)],
|
||||
vec![endpoint(test_id, provider_url)],
|
||||
vec![key(test_id)],
|
||||
));
|
||||
@@ -469,7 +560,7 @@ async fn ai_execute_pii_redaction_disabled_module_passes_original_chat_through()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ai_execute_pii_redaction_disabled_provider_passes_original_chat_through() {
|
||||
async fn ai_execute_pii_redaction_disabled_feature_passes_original_chat_through() {
|
||||
let (response_json, seen) = run_sync_redaction_case(
|
||||
"ai-execute-pii-redaction-disabled-provider",
|
||||
true,
|
||||
@@ -492,13 +583,13 @@ async fn ai_execute_pii_redaction_disabled_provider_passes_original_chat_through
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ai_execute_pii_redaction_empty_entities_passes_original_chat_through() {
|
||||
async fn ai_execute_pii_redaction_empty_rules_passes_original_chat_through() {
|
||||
let (response_json, seen) = run_sync_redaction_case_with_system_config(
|
||||
"ai-execute-pii-redaction-empty-entities",
|
||||
true,
|
||||
"pass_through",
|
||||
rich_pii_request(),
|
||||
redaction_config_with_entities(true, json!([])),
|
||||
redaction_config_with_rules(true, json!([])),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -586,13 +677,8 @@ async fn ai_execute_pii_redaction_restores_executed_candidate_session_after_late
|
||||
}),
|
||||
);
|
||||
let (provider_url, provider_handle) = start_server(provider_app).await;
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key("sk-client-redaction-candidate-session")),
|
||||
auth_snapshot(
|
||||
"api-key-redaction-candidate-session",
|
||||
"user-redaction-candidate-session",
|
||||
),
|
||||
)]));
|
||||
let auth_repository =
|
||||
auth_repository_with_redaction_feature_settings("redaction-candidate-session", true, true);
|
||||
let mut later_candidate = candidate_row("redaction-candidate-session");
|
||||
later_candidate.provider_id = "provider-redaction-candidate-session-later".to_string();
|
||||
later_candidate.endpoint_id = "endpoint-redaction-candidate-session-later".to_string();
|
||||
@@ -702,10 +788,8 @@ async fn pii_redaction_performance_limits_do_not_forward_unredacted_body_upstrea
|
||||
}),
|
||||
);
|
||||
let (provider_url, provider_handle) = start_server(provider_app).await;
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key("sk-client-pii-redaction-limit")),
|
||||
auth_snapshot("api-key-pii-redaction-limit", "user-pii-redaction-limit"),
|
||||
)]));
|
||||
let auth_repository =
|
||||
auth_repository_with_redaction_feature_settings("pii-redaction-limit", true, true);
|
||||
let candidate_selection_repository =
|
||||
Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
|
||||
candidate_row("pii-redaction-limit"),
|
||||
@@ -777,10 +861,7 @@ async fn ai_execute_pii_redaction_missing_encryption_key_fails_closed_before_pro
|
||||
);
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let test_id = "ai-execute-pii-redaction-missing-encryption-key";
|
||||
let auth_repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key(&format!("sk-client-{test_id}"))),
|
||||
auth_snapshot(&format!("api-key-{test_id}"), &format!("user-{test_id}")),
|
||||
)]));
|
||||
let auth_repository = auth_repository_with_redaction_feature_settings(test_id, true, true);
|
||||
let candidate_selection_repository =
|
||||
Arc::new(InMemoryMinimalCandidateSelectionReadRepository::seed(vec![
|
||||
candidate_row(test_id),
|
||||
@@ -791,17 +872,16 @@ async fn ai_execute_pii_redaction_missing_encryption_key_fails_closed_before_pro
|
||||
vec![endpoint(test_id, "https://example.com".to_string())],
|
||||
vec![key(test_id)],
|
||||
));
|
||||
let data_state = crate::data::GatewayDataState::with_auth_candidate_selection_provider_catalog_and_request_candidate_repository_for_tests(
|
||||
auth_repository,
|
||||
candidate_selection_repository,
|
||||
provider_catalog_repository,
|
||||
Arc::clone(&request_candidate_repository),
|
||||
"",
|
||||
)
|
||||
.with_system_config_values_for_tests(redaction_config(true));
|
||||
let gateway_state = build_state_with_execution_runtime_override(execution_runtime_url.clone())
|
||||
.with_data_state_for_tests(
|
||||
crate::data::GatewayDataState::with_auth_candidate_selection_provider_catalog_and_request_candidate_repository_for_tests(
|
||||
auth_repository,
|
||||
candidate_selection_repository,
|
||||
provider_catalog_repository,
|
||||
Arc::clone(&request_candidate_repository),
|
||||
"",
|
||||
)
|
||||
.with_system_config_values_for_tests(redaction_config(true)),
|
||||
);
|
||||
.with_data_state_for_tests(data_state);
|
||||
let gateway = build_router_with_state(gateway_state);
|
||||
let (gateway_url, gateway_handle) = start_server(gateway).await;
|
||||
|
||||
|
||||
@@ -785,7 +785,7 @@ async fn gateway_handles_admin_modules_status_locally_with_trusted_admin_princip
|
||||
assert_eq!(payload["chat_pii_redaction"]["enabled"], json!(false));
|
||||
assert_eq!(
|
||||
payload["chat_pii_redaction"]["display_name"],
|
||||
"敏感信息替换保护"
|
||||
"敏感信息保护"
|
||||
);
|
||||
assert_eq!(
|
||||
payload["chat_pii_redaction"]["config_validated"],
|
||||
@@ -967,7 +967,7 @@ async fn gateway_handles_chat_pii_redaction_module_status_detail_locally_with_tr
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
assert_eq!(payload["name"], "chat_pii_redaction");
|
||||
assert_eq!(payload["display_name"], "敏感信息替换保护");
|
||||
assert_eq!(payload["display_name"], "敏感信息保护");
|
||||
assert_eq!(payload["enabled"], json!(true));
|
||||
assert_eq!(payload["active"], json!(true));
|
||||
assert_eq!(payload["config_validated"], json!(true));
|
||||
|
||||
@@ -1392,34 +1392,23 @@ async fn gateway_validates_chat_pii_redaction_system_config_locally_with_trusted
|
||||
get_config("module.chat_pii_redaction.enabled").await["value"],
|
||||
json!(false)
|
||||
);
|
||||
assert_eq!(
|
||||
get_config("module.chat_pii_redaction.provider_scope").await["value"],
|
||||
json!("selected_providers")
|
||||
);
|
||||
assert_eq!(
|
||||
get_config("module.chat_pii_redaction.inject_model_instruction").await["value"],
|
||||
json!(true)
|
||||
);
|
||||
assert_eq!(
|
||||
get_config("module.chat_pii_redaction.cache_ttl_seconds").await["value"],
|
||||
json!(300)
|
||||
);
|
||||
assert_eq!(
|
||||
get_config("module.chat_pii_redaction.entities").await["value"],
|
||||
json!([
|
||||
"email",
|
||||
"cn_phone",
|
||||
"global_phone",
|
||||
"cn_id",
|
||||
"payment_card",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"api_key",
|
||||
"access_token",
|
||||
"secret_key",
|
||||
"bearer_token",
|
||||
"jwt"
|
||||
])
|
||||
get_config("module.chat_pii_redaction.placeholder_prefix").await["value"],
|
||||
json!("AETHER")
|
||||
);
|
||||
let default_rules_payload = get_config("module.chat_pii_redaction.rules").await;
|
||||
let default_rules = default_rules_payload["value"]
|
||||
.as_array()
|
||||
.expect("default rules should be an array");
|
||||
assert!(
|
||||
default_rules.iter().any(|rule| {
|
||||
rule["name"] == json!("手机号") && rule["features"]["validator"] == json!("cn_phone")
|
||||
}),
|
||||
"default rules should include 手机号"
|
||||
);
|
||||
|
||||
let enabled_response = put_config("module.chat_pii_redaction.enabled", json!(true)).await;
|
||||
@@ -1430,29 +1419,34 @@ async fn gateway_validates_chat_pii_redaction_system_config_locally_with_trusted
|
||||
.expect("json body should parse");
|
||||
assert_eq!(enabled_payload["value"], json!(true));
|
||||
|
||||
let scope_response = put_config(
|
||||
"module.chat_pii_redaction.provider_scope",
|
||||
json!("all_providers"),
|
||||
let rules_response = put_config(
|
||||
"module.chat_pii_redaction.rules",
|
||||
json!([
|
||||
{
|
||||
"id": "email",
|
||||
"name": "邮箱",
|
||||
"pattern": r"(?i)[A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\.[A-Z]{2,63}",
|
||||
"enabled": true,
|
||||
"features": {"validator": "email"},
|
||||
"system": true
|
||||
},
|
||||
{
|
||||
"id": "custom_code",
|
||||
"name": "自定义规则",
|
||||
"pattern": r"CODE-\d{6}",
|
||||
"enabled": false,
|
||||
"features": {"validator": "custom_code", "experimental": true},
|
||||
"system": false
|
||||
}
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(scope_response.status(), StatusCode::OK);
|
||||
let scope_payload: serde_json::Value =
|
||||
scope_response.json().await.expect("json body should parse");
|
||||
assert_eq!(scope_payload["value"], json!("all_providers"));
|
||||
|
||||
let selected_entities_response = put_config(
|
||||
"module.chat_pii_redaction.entities",
|
||||
json!(["email", "jwt", "cn_phone"]),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(selected_entities_response.status(), StatusCode::OK);
|
||||
let selected_entities_payload: serde_json::Value = selected_entities_response
|
||||
.json()
|
||||
.await
|
||||
.expect("json body should parse");
|
||||
assert_eq!(rules_response.status(), StatusCode::OK);
|
||||
let rules_payload: serde_json::Value =
|
||||
rules_response.json().await.expect("json body should parse");
|
||||
assert_eq!(
|
||||
selected_entities_payload["value"],
|
||||
json!(["email", "cn_phone", "jwt"])
|
||||
rules_payload["value"][1]["features"]["experimental"],
|
||||
json!(true)
|
||||
);
|
||||
|
||||
let ttl_response = put_config("module.chat_pii_redaction.cache_ttl_seconds", json!(3600)).await;
|
||||
@@ -1460,45 +1454,40 @@ async fn gateway_validates_chat_pii_redaction_system_config_locally_with_trusted
|
||||
let ttl_payload: serde_json::Value = ttl_response.json().await.expect("json body should parse");
|
||||
assert_eq!(ttl_payload["value"], json!(3600));
|
||||
|
||||
let instruction_response = put_config(
|
||||
"module.chat_pii_redaction.inject_model_instruction",
|
||||
json!(false),
|
||||
let prefix_response = put_config(
|
||||
"module.chat_pii_redaction.placeholder_prefix",
|
||||
json!("vendor_safe"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(instruction_response.status(), StatusCode::OK);
|
||||
let instruction_payload: serde_json::Value = instruction_response
|
||||
assert_eq!(prefix_response.status(), StatusCode::OK);
|
||||
let prefix_payload: serde_json::Value = prefix_response
|
||||
.json()
|
||||
.await
|
||||
.expect("json body should parse");
|
||||
assert_eq!(instruction_payload["value"], json!(false));
|
||||
assert_eq!(prefix_payload["value"], json!("VENDOR_SAFE"));
|
||||
|
||||
let invalid_scope_response = put_config(
|
||||
"module.chat_pii_redaction.provider_scope",
|
||||
json!("enabled_providers"),
|
||||
let invalid_prefix_response = put_config(
|
||||
"module.chat_pii_redaction.placeholder_prefix",
|
||||
json!("bad-prefix"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(invalid_scope_response.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(invalid_prefix_response.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let invalid_entities_response = put_config(
|
||||
"module.chat_pii_redaction.entities",
|
||||
json!(["email", "name"]),
|
||||
let invalid_rules_response = put_config(
|
||||
"module.chat_pii_redaction.rules",
|
||||
json!([
|
||||
{
|
||||
"id": "broken",
|
||||
"name": "坏规则",
|
||||
"pattern": "[",
|
||||
"enabled": true,
|
||||
"features": {"validator": "broken"},
|
||||
"system": false
|
||||
}
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(invalid_entities_response.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let invalid_ttl_response =
|
||||
put_config("module.chat_pii_redaction.cache_ttl_seconds", json!(600)).await;
|
||||
assert_eq!(invalid_ttl_response.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let invalid_instruction_response = put_config(
|
||||
"module.chat_pii_redaction.inject_model_instruction",
|
||||
json!("yes"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
invalid_instruction_response.status(),
|
||||
StatusCode::BAD_REQUEST
|
||||
);
|
||||
assert_eq!(invalid_rules_response.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let enabled_default_response =
|
||||
put_config("module.chat_pii_redaction.enabled", serde_json::Value::Null).await;
|
||||
@@ -1509,45 +1498,21 @@ async fn gateway_validates_chat_pii_redaction_system_config_locally_with_trusted
|
||||
.expect("json body should parse");
|
||||
assert_eq!(enabled_default_payload["value"], json!(false));
|
||||
|
||||
let scope_default_response = put_config(
|
||||
"module.chat_pii_redaction.provider_scope",
|
||||
serde_json::Value::Null,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(scope_default_response.status(), StatusCode::OK);
|
||||
let scope_default_payload: serde_json::Value = scope_default_response
|
||||
let rules_default_response =
|
||||
put_config("module.chat_pii_redaction.rules", serde_json::Value::Null).await;
|
||||
assert_eq!(rules_default_response.status(), StatusCode::OK);
|
||||
let rules_default_payload: serde_json::Value = rules_default_response
|
||||
.json()
|
||||
.await
|
||||
.expect("json body should parse");
|
||||
assert_eq!(scope_default_payload["value"], json!("selected_providers"));
|
||||
assert!(!rules_default_payload["value"]
|
||||
.as_array()
|
||||
.expect("default rules should be an array")
|
||||
.is_empty());
|
||||
|
||||
let entities_default_response = put_config(
|
||||
"module.chat_pii_redaction.entities",
|
||||
serde_json::Value::Null,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(entities_default_response.status(), StatusCode::OK);
|
||||
let entities_default_payload: serde_json::Value = entities_default_response
|
||||
.json()
|
||||
.await
|
||||
.expect("json body should parse");
|
||||
assert_eq!(
|
||||
entities_default_payload["value"],
|
||||
json!([
|
||||
"email",
|
||||
"cn_phone",
|
||||
"global_phone",
|
||||
"cn_id",
|
||||
"payment_card",
|
||||
"ipv4",
|
||||
"ipv6",
|
||||
"api_key",
|
||||
"access_token",
|
||||
"secret_key",
|
||||
"bearer_token",
|
||||
"jwt"
|
||||
])
|
||||
);
|
||||
let invalid_ttl_response =
|
||||
put_config("module.chat_pii_redaction.cache_ttl_seconds", json!(600)).await;
|
||||
assert_eq!(invalid_ttl_response.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let ttl_default_response = put_config(
|
||||
"module.chat_pii_redaction.cache_ttl_seconds",
|
||||
@@ -1561,17 +1526,17 @@ async fn gateway_validates_chat_pii_redaction_system_config_locally_with_trusted
|
||||
.expect("json body should parse");
|
||||
assert_eq!(ttl_default_payload["value"], json!(300));
|
||||
|
||||
let instruction_default_response = put_config(
|
||||
"module.chat_pii_redaction.inject_model_instruction",
|
||||
let prefix_default_response = put_config(
|
||||
"module.chat_pii_redaction.placeholder_prefix",
|
||||
serde_json::Value::Null,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(instruction_default_response.status(), StatusCode::OK);
|
||||
let instruction_default_payload: serde_json::Value = instruction_default_response
|
||||
assert_eq!(prefix_default_response.status(), StatusCode::OK);
|
||||
let prefix_default_payload: serde_json::Value = prefix_default_response
|
||||
.json()
|
||||
.await
|
||||
.expect("json body should parse");
|
||||
assert_eq!(instruction_default_payload["value"], json!(true));
|
||||
assert_eq!(prefix_default_payload["value"], json!("AETHER"));
|
||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
|
||||
@@ -4274,6 +4274,12 @@ async fn gateway_updates_users_me_detail_locally_without_proxying_upstream() {
|
||||
.json(&json!({
|
||||
"email": "alice+updated@example.com",
|
||||
"username": "alice-updated",
|
||||
"feature_settings": {
|
||||
"chat_pii_redaction": {
|
||||
"enabled": true,
|
||||
"inject_model_instruction": false
|
||||
}
|
||||
}
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
@@ -4301,6 +4307,14 @@ async fn gateway_updates_users_me_detail_locally_without_proxying_upstream() {
|
||||
assert_eq!(get_payload["username"], "alice-updated");
|
||||
assert_eq!(get_payload["auth_source"], "local");
|
||||
assert_eq!(get_payload["has_password"], true);
|
||||
assert_eq!(
|
||||
get_payload["feature_settings"]["chat_pii_redaction"]["enabled"],
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
get_payload["feature_settings"]["chat_pii_redaction"]["inject_model_instruction"],
|
||||
false
|
||||
);
|
||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
@@ -6334,6 +6348,7 @@ async fn gateway_handles_users_me_api_key_writes_locally_without_proxying_upstre
|
||||
assert_eq!(create_payload["name"], "writer-key");
|
||||
assert_eq!(create_payload["rate_limit"], 120);
|
||||
assert_eq!(create_payload["concurrent_limit"], serde_json::Value::Null);
|
||||
assert_eq!(create_payload["feature_settings"], serde_json::Value::Null);
|
||||
assert_eq!(create_payload["message"], "API密钥创建成功");
|
||||
let created_at = create_payload["created_at"]
|
||||
.as_str()
|
||||
@@ -6353,7 +6368,13 @@ async fn gateway_handles_users_me_api_key_writes_locally_without_proxying_upstre
|
||||
.json(&json!({
|
||||
"name": "writer-key-renamed",
|
||||
"rate_limit": 30,
|
||||
"concurrent_limit": 4
|
||||
"concurrent_limit": 4,
|
||||
"feature_settings": {
|
||||
"chat_pii_redaction": {
|
||||
"enabled": true,
|
||||
"inject_model_instruction": false
|
||||
}
|
||||
}
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
@@ -6366,6 +6387,14 @@ async fn gateway_handles_users_me_api_key_writes_locally_without_proxying_upstre
|
||||
assert_eq!(update_payload["name"], "writer-key-renamed");
|
||||
assert_eq!(update_payload["rate_limit"], 30);
|
||||
assert_eq!(update_payload["concurrent_limit"], 4);
|
||||
assert_eq!(
|
||||
update_payload["feature_settings"]["chat_pii_redaction"]["enabled"],
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
update_payload["feature_settings"]["chat_pii_redaction"]["inject_model_instruction"],
|
||||
false
|
||||
);
|
||||
assert_eq!(update_payload["message"], "API密钥已更新");
|
||||
|
||||
let toggle_response = client
|
||||
@@ -6446,6 +6475,10 @@ async fn gateway_handles_users_me_api_key_writes_locally_without_proxying_upstre
|
||||
assert_eq!(detail_payload["concurrent_limit"], 4);
|
||||
assert_eq!(detail_payload["force_capabilities"], json!({}));
|
||||
assert_eq!(detail_payload["created_at"], created_at);
|
||||
assert_eq!(
|
||||
detail_payload["feature_settings"]["chat_pii_redaction"]["enabled"],
|
||||
true
|
||||
);
|
||||
|
||||
let delete_response = client
|
||||
.delete(format!("{gateway_url}/api/users/me/api-keys/{created_id}"))
|
||||
|
||||
Reference in New Issue
Block a user