mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
fix: answer 400 for bad role updates and stop time-range overflow and PromQL time panics (#16190)
This commit is contained in:
@@ -224,8 +224,10 @@ pub async fn get_alert_history(
|
||||
Some(&user_email.user_id),
|
||||
)
|
||||
.await;
|
||||
if max_query_range > 0 && (end_time - start_time) > max_query_range * 3600 * 1_000_000 {
|
||||
start_time = end_time - max_query_range * 3600 * 1_000_000;
|
||||
if let Some(clamped) =
|
||||
config::utils::query_range::clamp_start_time(start_time, end_time, max_query_range)
|
||||
{
|
||||
start_time = clamped;
|
||||
log::warn!(
|
||||
"Start time for alert History api for org {org_id} updated as per max query range set for triggers stream"
|
||||
)
|
||||
|
||||
@@ -396,6 +396,7 @@ pub async fn get_roles(Path(_org_id): Path<String>) -> Response {
|
||||
request_body(content = inline(RoleRequest), description = "RoleRequest", content_type = "application/json"),
|
||||
responses(
|
||||
(status = 200, description = "Success", content_type = "application/json", body = Object),
|
||||
(status = 400, description = "Invalid role name, permission or user id; nothing is applied if any entry is invalid", content_type = "application/json", body = ()),
|
||||
(status = 500, description = "Failure", content_type = "application/json", body = ()),
|
||||
),
|
||||
extensions(
|
||||
@@ -407,6 +408,16 @@ pub async fn update_role(
|
||||
Path((org_id, role_id)): Path<(String, String)>,
|
||||
Json(update_role): Json<RoleRequest>,
|
||||
) -> Response {
|
||||
if let Err(msg) = o2_openfga::authorizer::roles::validate_role_update(
|
||||
&org_id,
|
||||
&role_id,
|
||||
&update_role.add,
|
||||
&update_role.remove,
|
||||
update_role.add_users.as_ref(),
|
||||
update_role.remove_users.as_ref(),
|
||||
) {
|
||||
return MetaHttpResponse::bad_request(msg);
|
||||
}
|
||||
match o2_openfga::authorizer::roles::update_role(
|
||||
&org_id,
|
||||
&role_id,
|
||||
@@ -1478,28 +1489,34 @@ mod tests {
|
||||
#[cfg(feature = "enterprise")]
|
||||
{
|
||||
init_ofga_test();
|
||||
let role_request = o2_dex::meta::auth::RoleRequest {
|
||||
add: vec![o2_dex::meta::auth::O2EntityAuthorization {
|
||||
object: "permission1".to_string(),
|
||||
permission: o2_dex::meta::auth::Permission::AllowAll,
|
||||
}],
|
||||
remove: vec![o2_dex::meta::auth::O2EntityAuthorization {
|
||||
object: "permission2".to_string(),
|
||||
permission: o2_dex::meta::auth::Permission::AllowAll,
|
||||
}],
|
||||
let grant = |object: &str| o2_dex::meta::auth::O2EntityAuthorization {
|
||||
object: object.to_string(),
|
||||
permission: o2_dex::meta::auth::Permission::AllowAll,
|
||||
};
|
||||
let role_request = |object: &str| o2_dex::meta::auth::RoleRequest {
|
||||
add: vec![grant(object)],
|
||||
remove: vec![grant("stream:permission2")],
|
||||
add_users: Some(HashSet::from_iter(vec!["user1".to_string()])),
|
||||
remove_users: Some(HashSet::from_iter(vec!["user2".to_string()])),
|
||||
};
|
||||
|
||||
let resp = update_role(
|
||||
Path(("test_org".to_string(), "test_role".to_string())),
|
||||
Json(role_request),
|
||||
Json(role_request("stream:permission1")),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
extract_status_code(&resp),
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
);
|
||||
|
||||
// A type the model lacks is the client's error, not OpenFGA's 400 surfaced as a 500.
|
||||
let resp = update_role(
|
||||
Path(("test_org".to_string(), "test_role".to_string())),
|
||||
Json(role_request("no_such_type:permission1")),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(extract_status_code(&resp), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
|
||||
@@ -568,11 +568,7 @@ async fn clamp_to_max_query_range(org_id: &str, user_id: &str, start: i64, end:
|
||||
};
|
||||
let max_hours =
|
||||
get_settings_max_query_range(settings.max_query_range, org_id, Some(user_id)).await;
|
||||
if max_hours > 0 && (end - start) > max_hours * 3600 * 1_000_000 {
|
||||
end - max_hours * 3600 * 1_000_000
|
||||
} else {
|
||||
start
|
||||
}
|
||||
config::utils::query_range::clamp_start_time(start, end, max_hours).unwrap_or(start)
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
|
||||
@@ -1096,8 +1096,10 @@ pub async fn get_workflow_history(
|
||||
Some(&user_email.user_id),
|
||||
)
|
||||
.await;
|
||||
if max_query_range > 0 && (end_time - start_time) > max_query_range * 3600 * 1_000_000 {
|
||||
start_time = end_time - max_query_range * 3600 * 1_000_000;
|
||||
if let Some(clamped) =
|
||||
config::utils::query_range::clamp_start_time(start_time, end_time, max_query_range)
|
||||
{
|
||||
start_time = clamped;
|
||||
log::warn!(
|
||||
"Start time for alert History api for org {org_id} updated as per max query range set for triggers stream"
|
||||
)
|
||||
|
||||
@@ -154,8 +154,10 @@ pub async fn get_pipeline_history(
|
||||
Some(&user_email.user_id),
|
||||
)
|
||||
.await;
|
||||
if max_query_range > 0 && (end_time - start_time) > max_query_range * 3600 * 1_000_000 {
|
||||
start_time = end_time - max_query_range * 3600 * 1_000_000;
|
||||
if let Some(clamped) =
|
||||
config::utils::query_range::clamp_start_time(start_time, end_time, max_query_range)
|
||||
{
|
||||
start_time = clamped;
|
||||
log::warn!(
|
||||
"Start time for pipeline History api for org {org_id} updated as per max query range set for triggers stream"
|
||||
)
|
||||
|
||||
@@ -541,6 +541,16 @@ async fn query_range(
|
||||
}
|
||||
},
|
||||
};
|
||||
if let Err(e) = validate_range_window(start, end) {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
axum::Json(config::meta::promql::ApiFuncResponse::<()>::err_bad_data(
|
||||
e,
|
||||
Some(trace_id),
|
||||
)),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
if step == 0 {
|
||||
step = promql::round_step((end - start) / promql::MAX_DATA_POINTS);
|
||||
}
|
||||
@@ -1201,6 +1211,19 @@ fn validate_metadata_params(
|
||||
Ok((selector, start, end))
|
||||
}
|
||||
|
||||
fn validate_range_window(start: i64, end: i64) -> Result<(), String> {
|
||||
// `end - start` and the gRPC epoch conversion both overflow on a negative or reversed window.
|
||||
if start < 0 {
|
||||
return Err(format!(
|
||||
"start must not be before the Unix epoch, got {start}"
|
||||
));
|
||||
}
|
||||
if end < start {
|
||||
return Err(format!("end {end} must not be before start {start}"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// prometheus formatting query expressions
|
||||
|
||||
// refer: https://prometheus.io/docs/prometheus/latest/querying/api/#formatting-query-expressions
|
||||
@@ -1750,6 +1773,15 @@ impl promql_parser::util::ExprVisitor for MaxLookbackWindowVisitor {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn validate_range_window_rejects_negative_and_inverted_windows() {
|
||||
assert!(validate_range_window(0, 0).is_ok());
|
||||
assert!(validate_range_window(1_000, 2_000).is_ok());
|
||||
assert!(validate_range_window(-1, 10).is_err());
|
||||
assert!(validate_range_window(i64::MIN, 10).is_err());
|
||||
assert!(validate_range_window(10, 9).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metric_reads_check_permission_for_every_non_root_user() {
|
||||
let text = include_str!("mod.rs");
|
||||
|
||||
@@ -320,10 +320,13 @@ pub async fn search(
|
||||
.await;
|
||||
// Enrichment tables have no retention and must be searched in full; skip the clamp.
|
||||
if stream_type != StreamType::EnrichmentTables
|
||||
&& max_query_range > 0
|
||||
&& (req.query.end_time - req.query.start_time) > max_query_range * 3600 * 1_000_000
|
||||
&& let Some(clamped) = config::utils::query_range::clamp_start_time(
|
||||
req.query.start_time,
|
||||
req.query.end_time,
|
||||
max_query_range,
|
||||
)
|
||||
{
|
||||
req.query.start_time = req.query.end_time - max_query_range * 3600 * 1_000_000;
|
||||
req.query.start_time = clamped;
|
||||
range_error = format!(
|
||||
"Query duration is modified due to query range restriction of {max_query_range} hours"
|
||||
);
|
||||
@@ -1807,10 +1810,13 @@ pub async fn result_schema(
|
||||
.await;
|
||||
// Enrichment tables have no retention and must be searched in full; skip the clamp.
|
||||
if stream_type != StreamType::EnrichmentTables
|
||||
&& max_query_range > 0
|
||||
&& (req.query.end_time - req.query.start_time) > max_query_range * 3600 * 1_000_000
|
||||
&& let Some(clamped) = config::utils::query_range::clamp_start_time(
|
||||
req.query.start_time,
|
||||
req.query.end_time,
|
||||
max_query_range,
|
||||
)
|
||||
{
|
||||
req.query.start_time = req.query.end_time - max_query_range * 3600 * 1_000_000;
|
||||
req.query.start_time = clamped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,10 +256,13 @@ pub async fn search_multi(
|
||||
.await;
|
||||
// Enrichment tables have no retention and must be searched in full; skip the clamp.
|
||||
if stream_type != StreamType::EnrichmentTables
|
||||
&& max_query_range > 0
|
||||
&& (req.query.end_time - req.query.start_time) > max_query_range * 3600 * 1_000_000
|
||||
&& let Some(clamped) = config::utils::query_range::clamp_start_time(
|
||||
req.query.start_time,
|
||||
req.query.end_time,
|
||||
max_query_range,
|
||||
)
|
||||
{
|
||||
req.query.start_time = req.query.end_time - max_query_range * 3600 * 1_000_000;
|
||||
req.query.start_time = clamped;
|
||||
range_error = format!(
|
||||
"{} Query duration for stream {} is modified due to query range restriction of {} hours",
|
||||
range_error, stream_name, max_query_range
|
||||
|
||||
@@ -181,10 +181,12 @@ pub async fn get_search_profile(
|
||||
let max_query_range =
|
||||
get_settings_max_query_range(settings.max_query_range, meta_org_id, Some(&user_id))
|
||||
.await;
|
||||
if max_query_range > 0
|
||||
&& (req.query.end_time - req.query.start_time) > max_query_range * 3600 * 1_000_000
|
||||
{
|
||||
req.query.start_time = req.query.end_time - max_query_range * 3600 * 1_000_000;
|
||||
if let Some(clamped) = config::utils::query_range::clamp_start_time(
|
||||
req.query.start_time,
|
||||
req.query.end_time,
|
||||
max_query_range,
|
||||
) {
|
||||
req.query.start_time = clamped;
|
||||
range_error = format!(
|
||||
"Query duration is modified due to query range restriction of {max_query_range} hours"
|
||||
);
|
||||
|
||||
@@ -198,8 +198,10 @@ impl TraceListOptions {
|
||||
}
|
||||
|
||||
pub fn restrict_range(&mut self, max_hours: i64) {
|
||||
if max_hours > 0 && (self.range.1 - self.range.0) > max_hours * 3600 * 1_000_000 {
|
||||
self.range.0 = self.range.1 - max_hours * 3600 * 1_000_000;
|
||||
if let Some(clamped) =
|
||||
config::utils::query_range::clamp_start_time(self.range.0, self.range.1, max_hours)
|
||||
{
|
||||
self.range.0 = clamped;
|
||||
self.range_error = format!(
|
||||
"Query duration is modified due to query range restriction of {max_hours} hours"
|
||||
);
|
||||
|
||||
@@ -188,8 +188,10 @@ pub async fn get_latest_sessions(
|
||||
)
|
||||
.await;
|
||||
let mut range_error = String::new();
|
||||
if max_query_range > 0 && (end_time - start_time) > max_query_range * 3600 * 1_000_000 {
|
||||
start_time = end_time - max_query_range * 3600 * 1_000_000;
|
||||
if let Some(clamped) =
|
||||
config::utils::query_range::clamp_start_time(start_time, end_time, max_query_range)
|
||||
{
|
||||
start_time = clamped;
|
||||
range_error = format!(
|
||||
"Query duration is modified due to query range restriction of {max_query_range} hours"
|
||||
);
|
||||
|
||||
@@ -155,8 +155,10 @@ pub async fn get_latest_users(
|
||||
)
|
||||
.await;
|
||||
let mut range_error = String::new();
|
||||
if max_query_range > 0 && (end_time - start_time) > max_query_range * 3600 * 1_000_000 {
|
||||
start_time = end_time - max_query_range * 3600 * 1_000_000;
|
||||
if let Some(clamped) =
|
||||
config::utils::query_range::clamp_start_time(start_time, end_time, max_query_range)
|
||||
{
|
||||
start_time = clamped;
|
||||
range_error = format!(
|
||||
"Query duration is modified due to query range restriction of {max_query_range} hours"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user