Fix legacy user policy modes for empty lists

This commit is contained in:
Entropy.Xu
2026-05-10 19:35:09 +08:00
parent 228253c166
commit 9e705ff603
10 changed files with 219 additions and 26 deletions

View File

@@ -0,0 +1,50 @@
UPDATE users
SET allowed_providers_mode = 'unrestricted'
WHERE allowed_providers_mode = 'specific'
AND (
allowed_providers IS NULL
OR TRIM(allowed_providers) = ''
OR CASE
WHEN JSON_VALID(allowed_providers) = 1 THEN
JSON_TYPE(allowed_providers) = 'NULL'
OR (
JSON_TYPE(allowed_providers) = 'ARRAY'
AND JSON_LENGTH(allowed_providers) = 0
)
ELSE FALSE
END
);
UPDATE users
SET allowed_api_formats_mode = 'unrestricted'
WHERE allowed_api_formats_mode = 'specific'
AND (
allowed_api_formats IS NULL
OR TRIM(allowed_api_formats) = ''
OR CASE
WHEN JSON_VALID(allowed_api_formats) = 1 THEN
JSON_TYPE(allowed_api_formats) = 'NULL'
OR (
JSON_TYPE(allowed_api_formats) = 'ARRAY'
AND JSON_LENGTH(allowed_api_formats) = 0
)
ELSE FALSE
END
);
UPDATE users
SET allowed_models_mode = 'unrestricted'
WHERE allowed_models_mode = 'specific'
AND (
allowed_models IS NULL
OR TRIM(allowed_models) = ''
OR CASE
WHEN JSON_VALID(allowed_models) = 1 THEN
JSON_TYPE(allowed_models) = 'NULL'
OR (
JSON_TYPE(allowed_models) = 'ARRAY'
AND JSON_LENGTH(allowed_models) = 0
)
ELSE FALSE
END
);

View File

@@ -0,0 +1,38 @@
UPDATE public.users
SET allowed_providers_mode = 'unrestricted'
WHERE allowed_providers_mode = 'specific'
AND (
allowed_providers IS NULL
OR json_typeof(allowed_providers) = 'null'
OR CASE
WHEN json_typeof(allowed_providers) = 'array' THEN
json_array_length(allowed_providers) = 0
ELSE FALSE
END
);
UPDATE public.users
SET allowed_api_formats_mode = 'unrestricted'
WHERE allowed_api_formats_mode = 'specific'
AND (
allowed_api_formats IS NULL
OR json_typeof(allowed_api_formats) = 'null'
OR CASE
WHEN json_typeof(allowed_api_formats) = 'array' THEN
json_array_length(allowed_api_formats) = 0
ELSE FALSE
END
);
UPDATE public.users
SET allowed_models_mode = 'unrestricted'
WHERE allowed_models_mode = 'specific'
AND (
allowed_models IS NULL
OR json_typeof(allowed_models) = 'null'
OR CASE
WHEN json_typeof(allowed_models) = 'array' THEN
json_array_length(allowed_models) = 0
ELSE FALSE
END
);

View File

@@ -0,0 +1,29 @@
UPDATE users
SET allowed_providers_mode = 'unrestricted'
WHERE allowed_providers_mode = 'specific'
AND (
allowed_providers IS NULL
OR trim(allowed_providers) = ''
OR lower(trim(allowed_providers)) = 'null'
OR trim(allowed_providers) = '[]'
);
UPDATE users
SET allowed_api_formats_mode = 'unrestricted'
WHERE allowed_api_formats_mode = 'specific'
AND (
allowed_api_formats IS NULL
OR trim(allowed_api_formats) = ''
OR lower(trim(allowed_api_formats)) = 'null'
OR trim(allowed_api_formats) = '[]'
);
UPDATE users
SET allowed_models_mode = 'unrestricted'
WHERE allowed_models_mode = 'specific'
AND (
allowed_models IS NULL
OR trim(allowed_models) = ''
OR lower(trim(allowed_models)) = 'null'
OR trim(allowed_models) = '[]'
);

View File

@@ -1351,7 +1351,11 @@ impl UserReadRepository for InMemoryUserReadRepository {
}
if allowed_providers_present {
user.allowed_providers = allowed_providers;
user.allowed_providers_mode = if user.allowed_providers.is_some() {
user.allowed_providers_mode = if user
.allowed_providers
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific".to_string()
} else {
"unrestricted".to_string()
@@ -1359,7 +1363,11 @@ impl UserReadRepository for InMemoryUserReadRepository {
}
if allowed_api_formats_present {
user.allowed_api_formats = allowed_api_formats;
user.allowed_api_formats_mode = if user.allowed_api_formats.is_some() {
user.allowed_api_formats_mode = if user
.allowed_api_formats
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific".to_string()
} else {
"unrestricted".to_string()
@@ -1367,7 +1375,11 @@ impl UserReadRepository for InMemoryUserReadRepository {
}
if allowed_models_present {
user.allowed_models = allowed_models;
user.allowed_models_mode = if user.allowed_models.is_some() {
user.allowed_models_mode = if user
.allowed_models
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific".to_string()
} else {
"unrestricted".to_string()

View File

@@ -1029,17 +1029,26 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
rate_limit: Option<i32>,
is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
let allowed_providers_mode = if allowed_providers.is_some() {
let allowed_providers_mode = if allowed_providers
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_api_formats_mode = if allowed_api_formats.is_some() {
let allowed_api_formats_mode = if allowed_api_formats
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_models_mode = if allowed_models.is_some() {
let allowed_models_mode = if allowed_models
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
@@ -1214,17 +1223,26 @@ VALUES (?, ?, ?, ?, ?, 'user', 'local', 'inherit', 'inherit', 'inherit', 'inheri
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
let user_id = uuid::Uuid::new_v4().to_string();
let now = chrono::Utc::now().timestamp();
let allowed_providers_mode = if allowed_providers.is_some() {
let allowed_providers_mode = if allowed_providers
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_api_formats_mode = if allowed_api_formats.is_some() {
let allowed_api_formats_mode = if allowed_api_formats
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_models_mode = if allowed_models.is_some() {
let allowed_models_mode = if allowed_models
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"

View File

@@ -1482,17 +1482,26 @@ WHERE id = $1
rate_limit: Option<i32>,
is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
let allowed_providers_mode = if allowed_providers.is_some() {
let allowed_providers_mode = if allowed_providers
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_api_formats_mode = if allowed_api_formats.is_some() {
let allowed_api_formats_mode = if allowed_api_formats
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_models_mode = if allowed_models.is_some() {
let allowed_models_mode = if allowed_models
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
@@ -1696,17 +1705,26 @@ VALUES (
rate_limit: Option<i32>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
let user_id = uuid::Uuid::new_v4().to_string();
let allowed_providers_mode = if allowed_providers.is_some() {
let allowed_providers_mode = if allowed_providers
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_api_formats_mode = if allowed_api_formats.is_some() {
let allowed_api_formats_mode = if allowed_api_formats
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_models_mode = if allowed_models.is_some() {
let allowed_models_mode = if allowed_models
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"

View File

@@ -1029,17 +1029,26 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
rate_limit: Option<i32>,
is_active: Option<bool>,
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
let allowed_providers_mode = if allowed_providers.is_some() {
let allowed_providers_mode = if allowed_providers
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_api_formats_mode = if allowed_api_formats.is_some() {
let allowed_api_formats_mode = if allowed_api_formats
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_models_mode = if allowed_models.is_some() {
let allowed_models_mode = if allowed_models
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
@@ -1214,17 +1223,26 @@ VALUES (?, ?, ?, ?, ?, 'user', 'local', 'inherit', 'inherit', 'inherit', 'inheri
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
let user_id = uuid::Uuid::new_v4().to_string();
let now = chrono::Utc::now().timestamp();
let allowed_providers_mode = if allowed_providers.is_some() {
let allowed_providers_mode = if allowed_providers
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_api_formats_mode = if allowed_api_formats.is_some() {
let allowed_api_formats_mode = if allowed_api_formats
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"
};
let allowed_models_mode = if allowed_models.is_some() {
let allowed_models_mode = if allowed_models
.as_ref()
.is_some_and(|values| !values.is_empty())
{
"specific"
} else {
"unrestricted"

View File

@@ -1024,7 +1024,7 @@ pub fn normalize_rate_limit_policy_mode(
}
fn legacy_list_policy_mode(values: &Option<Vec<String>>) -> String {
if values.is_some() {
if values.as_ref().is_some_and(|items| !items.is_empty()) {
"specific".to_string()
} else {
"unrestricted".to_string()
@@ -1096,8 +1096,8 @@ mod tests {
use serde_json::Value;
use super::{
StoredUserAuthRecord, StoredUserExportRow, StoredUserPreferenceRecord,
StoredUserSessionRecord,
legacy_list_policy_mode, StoredUserAuthRecord, StoredUserExportRow,
StoredUserPreferenceRecord, StoredUserSessionRecord,
};
#[test]
@@ -1208,6 +1208,16 @@ mod tests {
assert_eq!(row.allowed_models, Some(vec!["gpt-4.1".to_string()]));
}
#[test]
fn legacy_policy_mode_treats_empty_lists_as_unrestricted() {
assert_eq!(legacy_list_policy_mode(&None), "unrestricted");
assert_eq!(legacy_list_policy_mode(&Some(Vec::new())), "unrestricted");
assert_eq!(
legacy_list_policy_mode(&Some(vec!["openai".to_string()])),
"specific"
);
}
#[test]
fn user_session_previous_refresh_token_has_grace_window() {
let now = Utc::now();