Reject hidden user policy payloads

This commit is contained in:
RWDai
2026-05-13 17:56:18 +08:00
parent a99546b390
commit a1b0db38f5
5 changed files with 90 additions and 403 deletions

View File

@@ -1,6 +1,6 @@
use super::{ use super::{
build_admin_users_bad_request_response, build_admin_users_read_only_response, build_admin_users_bad_request_response, build_admin_users_read_only_response,
normalize_admin_user_api_formats, normalize_admin_user_role, normalize_admin_user_string_list, disabled_user_policy_detail, disabled_user_policy_field, normalize_admin_user_role,
}; };
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext}; use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
use crate::handlers::admin::shared::attach_admin_audit_response; use crate::handlers::admin::shared::attach_admin_audit_response;
@@ -85,14 +85,6 @@ struct ResolvedAdminUserSelection {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct AdminUserBatchMutation { struct AdminUserBatchMutation {
role: Option<String>, role: Option<String>,
allowed_providers_present: bool,
allowed_providers: Option<Vec<String>>,
allowed_api_formats_present: bool,
allowed_api_formats: Option<Vec<String>>,
allowed_models_present: bool,
allowed_models: Option<Vec<String>>,
rate_limit_present: bool,
rate_limit: Option<i32>,
is_active: Option<bool>, is_active: Option<bool>,
unlimited: Option<bool>, unlimited: Option<bool>,
modified_fields: Vec<&'static str>, modified_fields: Vec<&'static str>,
@@ -100,12 +92,7 @@ struct AdminUserBatchMutation {
impl AdminUserBatchMutation { impl AdminUserBatchMutation {
fn has_auth_user_fields(&self) -> bool { fn has_auth_user_fields(&self) -> bool {
self.role.is_some() self.role.is_some() || self.is_active.is_some()
|| self.allowed_providers_present
|| self.allowed_api_formats_present
|| self.allowed_models_present
|| self.rate_limit_present
|| self.is_active.is_some()
} }
} }
@@ -214,14 +201,14 @@ pub(in super::super) async fn build_admin_user_batch_action_response(
.update_local_auth_user_admin_fields( .update_local_auth_user_admin_fields(
&item.user_id, &item.user_id,
mutation.role.clone(), mutation.role.clone(),
mutation.allowed_providers_present, false,
mutation.allowed_providers.clone(), None,
mutation.allowed_api_formats_present, false,
mutation.allowed_api_formats.clone(), None,
mutation.allowed_models_present, false,
mutation.allowed_models.clone(), None,
mutation.rate_limit_present, false,
mutation.rate_limit, None,
mutation.is_active, mutation.is_active,
) )
.await? .await?
@@ -632,25 +619,8 @@ fn parse_access_control_mutation(payload: Option<Value>) -> Result<AdminUserBatc
}; };
let mut mutation = AdminUserBatchMutation::default(); let mut mutation = AdminUserBatchMutation::default();
if let Some(value) = payload.get("allowed_providers") { if let Some(field) = disabled_user_policy_field(&payload) {
mutation.allowed_providers_present = true; return Err(disabled_user_policy_detail(field));
mutation.allowed_providers = parse_optional_string_list(value, "allowed_providers")?;
mutation.modified_fields.push("allowed_providers");
}
if let Some(value) = payload.get("allowed_api_formats") {
mutation.allowed_api_formats_present = true;
mutation.allowed_api_formats = parse_optional_api_formats(value)?;
mutation.modified_fields.push("allowed_api_formats");
}
if let Some(value) = payload.get("allowed_models") {
mutation.allowed_models_present = true;
mutation.allowed_models = parse_optional_string_list(value, "allowed_models")?;
mutation.modified_fields.push("allowed_models");
}
if let Some(value) = payload.get("rate_limit") {
mutation.rate_limit_present = true;
mutation.rate_limit = parse_optional_rate_limit(value)?;
mutation.modified_fields.push("rate_limit");
} }
if let Some(value) = payload.get("unlimited") { if let Some(value) = payload.get("unlimited") {
mutation.unlimited = Some(parse_unlimited(value)?); mutation.unlimited = Some(parse_unlimited(value)?);
@@ -664,39 +634,6 @@ fn parse_access_control_mutation(payload: Option<Value>) -> Result<AdminUserBatc
Ok(mutation) Ok(mutation)
} }
fn parse_optional_string_list(
value: &Value,
field_name: &str,
) -> Result<Option<Vec<String>>, String> {
if value.is_null() {
return Ok(None);
}
let values = serde_json::from_value::<Vec<String>>(value.clone())
.map_err(|_| format!("{field_name} 必须是字符串数组或 null"))?;
normalize_admin_user_string_list(Some(values), field_name)
}
fn parse_optional_api_formats(value: &Value) -> Result<Option<Vec<String>>, String> {
if value.is_null() {
return Ok(None);
}
let values = serde_json::from_value::<Vec<String>>(value.clone())
.map_err(|_| "allowed_api_formats 必须是字符串数组或 null".to_string())?;
normalize_admin_user_api_formats(Some(values))
}
fn parse_optional_rate_limit(value: &Value) -> Result<Option<i32>, String> {
if value.is_null() {
return Ok(None);
}
let rate_limit = serde_json::from_value::<i32>(value.clone())
.map_err(|_| "rate_limit 必须是整数或 null".to_string())?;
if rate_limit < 0 {
return Err("rate_limit 必须大于等于 0".to_string());
}
Ok(Some(rate_limit))
}
fn parse_unlimited(value: &Value) -> Result<bool, String> { fn parse_unlimited(value: &Value) -> Result<bool, String> {
serde_json::from_value::<bool>(value.clone()).map_err(|_| "unlimited 必须是布尔值".to_string()) serde_json::from_value::<bool>(value.clone()).map_err(|_| "unlimited 必须是布尔值".to_string())
} }

View File

@@ -1,10 +1,8 @@
use super::super::{ use super::super::{
admin_default_user_initial_gift, build_admin_users_read_only_response, admin_default_user_initial_gift, build_admin_users_read_only_response,
legacy_admin_list_policy_mode, legacy_admin_rate_limit_policy_mode, disabled_user_policy_detail, disabled_user_policy_field, normalize_admin_optional_user_email,
normalize_admin_list_policy_mode, normalize_admin_optional_user_email, normalize_admin_user_group_ids, normalize_admin_user_role, normalize_admin_username,
normalize_admin_rate_limit_policy_mode, normalize_admin_user_api_formats, validate_admin_user_password, AdminCreateUserRequest,
normalize_admin_user_group_ids, normalize_admin_user_role, normalize_admin_user_string_list,
normalize_admin_username, validate_admin_user_password, AdminCreateUserRequest,
}; };
use super::support::{admin_user_password_policy, build_admin_user_payload_with_groups}; use super::support::{admin_user_password_policy, build_admin_user_payload_with_groups};
use crate::handlers::admin::request::{AdminAppState, AdminRequestContext}; use crate::handlers::admin::request::{AdminAppState, AdminRequestContext};
@@ -16,7 +14,7 @@ use axum::{
response::{IntoResponse, Response}, response::{IntoResponse, Response},
Json, Json,
}; };
use serde_json::json; use serde_json::{json, Value};
pub(in super::super) async fn build_admin_create_user_response( pub(in super::super) async fn build_admin_create_user_response(
state: &AdminAppState<'_>, state: &AdminAppState<'_>,
@@ -40,7 +38,25 @@ pub(in super::super) async fn build_admin_create_user_response(
) )
.into_response()); .into_response());
}; };
let payload = match serde_json::from_slice::<AdminCreateUserRequest>(request_body) { let raw_payload = match serde_json::from_slice::<Value>(request_body) {
Ok(Value::Object(map)) => map,
_ => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "请求数据验证失败" })),
)
.into_response())
}
};
if let Some(field) = disabled_user_policy_field(&raw_payload) {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": disabled_user_policy_detail(field) })),
)
.into_response());
}
let payload = match serde_json::from_value::<AdminCreateUserRequest>(Value::Object(raw_payload))
{
Ok(value) => value, Ok(value) => value,
Err(_) => { Err(_) => {
return Ok(( return Ok((
@@ -89,13 +105,6 @@ pub(in super::super) async fn build_admin_create_user_response(
) )
.into_response()); .into_response());
} }
if payload.rate_limit.is_some_and(|value| value < 0) {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "rate_limit 必须大于等于 0" })),
)
.into_response());
}
if payload if payload
.initial_gift_usd .initial_gift_usd
.is_some_and(|value| !value.is_finite() || !(0.0..=10000.0).contains(&value)) .is_some_and(|value| !value.is_finite() || !(0.0..=10000.0).contains(&value))
@@ -106,90 +115,6 @@ pub(in super::super) async fn build_admin_create_user_response(
) )
.into_response()); .into_response());
} }
let allowed_providers =
match normalize_admin_user_string_list(payload.allowed_providers, "allowed_providers") {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
};
let allowed_api_formats = match normalize_admin_user_api_formats(payload.allowed_api_formats) {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
};
let allowed_models =
match normalize_admin_user_string_list(payload.allowed_models, "allowed_models") {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
};
let allowed_providers_mode = match payload.allowed_providers_mode.as_deref() {
Some(value) => match normalize_admin_list_policy_mode(value) {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
},
None => legacy_admin_list_policy_mode(&allowed_providers),
};
let allowed_api_formats_mode = match payload.allowed_api_formats_mode.as_deref() {
Some(value) => match normalize_admin_list_policy_mode(value) {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
},
None => legacy_admin_list_policy_mode(&allowed_api_formats),
};
let allowed_models_mode = match payload.allowed_models_mode.as_deref() {
Some(value) => match normalize_admin_list_policy_mode(value) {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
},
None => legacy_admin_list_policy_mode(&allowed_models),
};
let rate_limit_mode = match payload.rate_limit_mode.as_deref() {
Some(value) => match normalize_admin_rate_limit_policy_mode(value) {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
},
None => legacy_admin_rate_limit_policy_mode(payload.rate_limit),
};
let requested_group_ids = normalize_admin_user_group_ids(payload.group_ids); let requested_group_ids = normalize_admin_user_group_ids(payload.group_ids);
let group_ids = state let group_ids = state
.include_default_user_group_ids_for_role(&requested_group_ids, &role) .include_default_user_group_ids_for_role(&requested_group_ids, &role)
@@ -259,10 +184,10 @@ pub(in super::super) async fn build_admin_create_user_response(
username, username,
password_hash, password_hash,
role, role,
allowed_providers, None,
allowed_api_formats, None,
allowed_models, None,
payload.rate_limit, None,
) )
.await? .await?
else { else {
@@ -280,20 +205,6 @@ pub(in super::super) async fn build_admin_create_user_response(
"当前为只读模式,无法初始化用户钱包", "当前为只读模式,无法初始化用户钱包",
)); ));
} }
let Some(user) = state
.update_local_auth_user_policy_modes(
&user.id,
Some(allowed_providers_mode.clone()),
Some(allowed_api_formats_mode.clone()),
Some(allowed_models_mode.clone()),
Some(rate_limit_mode.clone()),
)
.await?
else {
return Ok(build_admin_users_read_only_response(
"当前为只读模式,无法创建用户",
));
};
if !group_ids.is_empty() { if !group_ids.is_empty() {
state state
.replace_user_groups_for_user(&user.id, &group_ids) .replace_user_groups_for_user(&user.id, &group_ids)
@@ -303,8 +214,8 @@ pub(in super::super) async fn build_admin_create_user_response(
Ok(attach_admin_audit_response( Ok(attach_admin_audit_response(
Json(build_admin_user_payload_with_groups( Json(build_admin_user_payload_with_groups(
&user, &user,
payload.rate_limit, None,
Some(rate_limit_mode.as_str()), None,
payload.unlimited, payload.unlimited,
&groups, &groups,
)) ))

View File

@@ -1,10 +1,8 @@
use super::super::{ use super::super::{
build_admin_users_bad_request_response, build_admin_users_data_unavailable_response, build_admin_users_bad_request_response, build_admin_users_data_unavailable_response,
build_admin_users_read_only_response, normalize_admin_list_policy_mode, build_admin_users_read_only_response, disabled_user_policy_detail, disabled_user_policy_field,
normalize_admin_optional_user_email, normalize_admin_rate_limit_policy_mode, normalize_admin_optional_user_email, normalize_admin_user_group_ids, normalize_admin_user_role,
normalize_admin_user_api_formats, normalize_admin_user_group_ids, normalize_admin_user_role, normalize_admin_username, validate_admin_user_password, AdminUpdateUserPatch,
normalize_admin_user_string_list, normalize_admin_username, validate_admin_user_password,
AdminUpdateUserPatch,
}; };
use super::support::{ use super::support::{
admin_user_id_from_detail_path, admin_user_password_policy, admin_user_id_from_detail_path, admin_user_password_policy,
@@ -53,6 +51,13 @@ pub(in super::super) async fn build_admin_update_user_response(
.into_response()) .into_response())
} }
}; };
if let Some(field) = disabled_user_policy_field(&raw_payload) {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": disabled_user_policy_detail(field) })),
)
.into_response());
}
let patch = match AdminUpdateUserPatch::from_object(raw_payload.clone()) { let patch = match AdminUpdateUserPatch::from_object(raw_payload.clone()) {
Ok(value) => value, Ok(value) => value,
Err(_) => { Err(_) => {
@@ -131,123 +136,6 @@ pub(in super::super) async fn build_admin_update_user_response(
None => None, None => None,
}; };
let effective_role = role.as_deref().unwrap_or(existing_user.role.as_str()); let effective_role = role.as_deref().unwrap_or(existing_user.role.as_str());
if payload.rate_limit.is_some_and(|value| value < 0) {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": "rate_limit 必须大于等于 0" })),
)
.into_response());
}
let allowed_providers = if field_presence.contains("allowed_providers") {
match normalize_admin_user_string_list(payload.allowed_providers, "allowed_providers") {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
}
} else {
None
};
let allowed_api_formats = if field_presence.contains("allowed_api_formats") {
match normalize_admin_user_api_formats(payload.allowed_api_formats) {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
}
} else {
None
};
let allowed_models = if field_presence.contains("allowed_models") {
match normalize_admin_user_string_list(payload.allowed_models, "allowed_models") {
Ok(value) => value,
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
}
} else {
None
};
let allowed_providers_mode = if field_presence.contains("allowed_providers_mode") {
match payload.allowed_providers_mode.as_deref() {
Some(value) => match normalize_admin_list_policy_mode(value) {
Ok(value) => Some(value),
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
},
None => None,
}
} else {
None
};
let allowed_api_formats_mode = if field_presence.contains("allowed_api_formats_mode") {
match payload.allowed_api_formats_mode.as_deref() {
Some(value) => match normalize_admin_list_policy_mode(value) {
Ok(value) => Some(value),
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
},
None => None,
}
} else {
None
};
let allowed_models_mode = if field_presence.contains("allowed_models_mode") {
match payload.allowed_models_mode.as_deref() {
Some(value) => match normalize_admin_list_policy_mode(value) {
Ok(value) => Some(value),
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
},
None => None,
}
} else {
None
};
let rate_limit_mode = if field_presence.contains("rate_limit_mode") {
match payload.rate_limit_mode.as_deref() {
Some(value) => match normalize_admin_rate_limit_policy_mode(value) {
Ok(value) => Some(value),
Err(detail) => {
return Ok((
http::StatusCode::BAD_REQUEST,
Json(json!({ "detail": detail })),
)
.into_response())
}
},
None => None,
}
} else {
None
};
let group_ids = if field_presence.contains("group_ids") { let group_ids = if field_presence.contains("group_ids") {
let requested_group_ids = normalize_admin_user_group_ids(payload.group_ids); let requested_group_ids = normalize_admin_user_group_ids(payload.group_ids);
Some( Some(
@@ -286,14 +174,6 @@ pub(in super::super) async fn build_admin_update_user_response(
|| username.is_some() || username.is_some()
|| payload.password.is_some() || payload.password.is_some()
|| role.is_some() || role.is_some()
|| field_presence.contains("allowed_providers")
|| allowed_providers_mode.is_some()
|| field_presence.contains("allowed_api_formats")
|| allowed_api_formats_mode.is_some()
|| field_presence.contains("allowed_models")
|| allowed_models_mode.is_some()
|| field_presence.contains("rate_limit")
|| rate_limit_mode.is_some()
|| payload.is_active.is_some() || payload.is_active.is_some()
|| group_ids.is_some(); || group_ids.is_some();
if needs_auth_user_write && !state.has_auth_user_write_capability() { if needs_auth_user_write && !state.has_auth_user_write_capability() {
@@ -358,25 +238,19 @@ pub(in super::super) async fn build_admin_update_user_response(
} }
} }
if role.is_some() if role.is_some() || payload.is_active.is_some() {
|| field_presence.contains("allowed_providers")
|| field_presence.contains("allowed_api_formats")
|| field_presence.contains("allowed_models")
|| field_presence.contains("rate_limit")
|| payload.is_active.is_some()
{
if state if state
.update_local_auth_user_admin_fields( .update_local_auth_user_admin_fields(
&user_id, &user_id,
role, role,
field_presence.contains("allowed_providers"), false,
allowed_providers, None,
field_presence.contains("allowed_api_formats"), false,
allowed_api_formats, None,
field_presence.contains("allowed_models"), false,
allowed_models, None,
field_presence.contains("rate_limit"), false,
payload.rate_limit, None,
payload.is_active, payload.is_active,
) )
.await? .await?
@@ -389,30 +263,6 @@ pub(in super::super) async fn build_admin_update_user_response(
.into_response()); .into_response());
} }
} }
if allowed_providers_mode.is_some()
|| allowed_api_formats_mode.is_some()
|| allowed_models_mode.is_some()
|| rate_limit_mode.is_some()
{
if state
.update_local_auth_user_policy_modes(
&user_id,
allowed_providers_mode,
allowed_api_formats_mode,
allowed_models_mode,
rate_limit_mode,
)
.await?
.is_none()
{
return Ok((
http::StatusCode::NOT_FOUND,
Json(json!({ "detail": "用户不存在" })),
)
.into_response());
}
}
if let Some(unlimited) = payload.unlimited { if let Some(unlimited) = payload.unlimited {
match state match state
.find_wallet(aether_data::repository::wallet::WalletLookupKey::UserId( .find_wallet(aether_data::repository::wallet::WalletLookupKey::UserId(
@@ -461,10 +311,7 @@ pub(in super::super) async fn build_admin_update_user_response(
.is_some_and(|wallet| wallet.limit_mode.eq_ignore_ascii_case("unlimited")); .is_some_and(|wallet| wallet.limit_mode.eq_ignore_ascii_case("unlimited"));
let export_row = find_admin_export_user(state, &user_id).await?; let export_row = find_admin_export_user(state, &user_id).await?;
let groups = state.list_user_groups_for_user(&user_id).await?; let groups = state.list_user_groups_for_user(&user_id).await?;
let rate_limit = export_row let rate_limit = export_row.as_ref().and_then(|row| row.rate_limit);
.as_ref()
.and_then(|row| row.rate_limit)
.or(payload.rate_limit);
Ok(attach_admin_audit_response( Ok(attach_admin_audit_response(
Json(build_admin_user_payload_with_groups( Json(build_admin_user_payload_with_groups(

View File

@@ -43,11 +43,11 @@ use self::shared::AdminUpdateUserPatch;
use self::shared::{ use self::shared::{
admin_default_user_initial_gift, build_admin_users_bad_request_response, admin_default_user_initial_gift, build_admin_users_bad_request_response,
build_admin_users_data_unavailable_response, build_admin_users_read_only_response, build_admin_users_data_unavailable_response, build_admin_users_read_only_response,
format_optional_datetime_iso8601, legacy_admin_list_policy_mode, disabled_user_policy_detail, disabled_user_policy_field, format_optional_datetime_iso8601,
legacy_admin_rate_limit_policy_mode, normalize_admin_optional_user_email, legacy_admin_list_policy_mode, legacy_admin_rate_limit_policy_mode,
normalize_admin_user_group_ids, normalize_admin_user_role, normalize_admin_username, normalize_admin_optional_user_email, normalize_admin_user_group_ids, normalize_admin_user_role,
validate_admin_user_password, AdminCreateUserApiKeyRequest, AdminCreateUserRequest, normalize_admin_username, validate_admin_user_password, AdminCreateUserApiKeyRequest,
AdminToggleUserApiKeyLockRequest, AdminUpdateUserApiKeyRequest, AdminCreateUserRequest, AdminToggleUserApiKeyLockRequest, AdminUpdateUserApiKeyRequest,
}; };
pub(crate) use self::shared::{ pub(crate) use self::shared::{
normalize_admin_list_policy_mode, normalize_admin_rate_limit_policy_mode, normalize_admin_list_policy_mode, normalize_admin_rate_limit_policy_mode,

View File

@@ -66,22 +66,6 @@ pub(super) struct AdminCreateUserRequest {
#[serde(default)] #[serde(default)]
pub(super) unlimited: bool, pub(super) unlimited: bool,
#[serde(default)] #[serde(default)]
pub(super) allowed_providers: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_providers_mode: Option<String>,
#[serde(default)]
pub(super) allowed_api_formats: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_api_formats_mode: Option<String>,
#[serde(default)]
pub(super) allowed_models: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_models_mode: Option<String>,
#[serde(default)]
pub(super) rate_limit: Option<i32>,
#[serde(default)]
pub(super) rate_limit_mode: Option<String>,
#[serde(default)]
pub(super) group_ids: Vec<String>, pub(super) group_ids: Vec<String>,
} }
@@ -98,22 +82,6 @@ pub(super) struct AdminUpdateUserRequest {
#[serde(default)] #[serde(default)]
pub(super) unlimited: Option<bool>, pub(super) unlimited: Option<bool>,
#[serde(default)] #[serde(default)]
pub(super) allowed_providers: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_providers_mode: Option<String>,
#[serde(default)]
pub(super) allowed_api_formats: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_api_formats_mode: Option<String>,
#[serde(default)]
pub(super) allowed_models: Option<Vec<String>>,
#[serde(default)]
pub(super) allowed_models_mode: Option<String>,
#[serde(default)]
pub(super) rate_limit: Option<i32>,
#[serde(default)]
pub(super) rate_limit_mode: Option<String>,
#[serde(default)]
pub(super) group_ids: Vec<String>, pub(super) group_ids: Vec<String>,
#[serde(default)] #[serde(default)]
pub(super) is_active: Option<bool>, pub(super) is_active: Option<bool>,
@@ -121,6 +89,30 @@ pub(super) struct AdminUpdateUserRequest {
pub(super) type AdminUpdateUserPatch = AdminTypedObjectPatch<AdminUpdateUserRequest>; pub(super) type AdminUpdateUserPatch = AdminTypedObjectPatch<AdminUpdateUserRequest>;
const DISABLED_USER_POLICY_FIELDS: &[&str] = &[
"allowed_providers",
"allowed_providers_mode",
"allowed_api_formats",
"allowed_api_formats_mode",
"allowed_models",
"allowed_models_mode",
"rate_limit",
"rate_limit_mode",
];
pub(super) fn disabled_user_policy_field(
object: &serde_json::Map<String, serde_json::Value>,
) -> Option<&'static str> {
DISABLED_USER_POLICY_FIELDS
.iter()
.copied()
.find(|field| object.contains_key(*field))
}
pub(super) fn disabled_user_policy_detail(field: &str) -> String {
format!("{field} 已停用,请通过用户分组管理访问权限")
}
pub(super) fn build_admin_users_data_unavailable_response() -> Response<Body> { pub(super) fn build_admin_users_data_unavailable_response() -> Response<Body> {
( (
http::StatusCode::SERVICE_UNAVAILABLE, http::StatusCode::SERVICE_UNAVAILABLE,