mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-26 20:15:07 +08:00
Merge #3945
3945: Do not leak field information on error r=Kerollmops a=vivek-26 # Pull Request ## Related issue Fixes #3865 ## What does this PR do? This PR ensures that `InvalidSortableAttribute`and `InvalidFacetSearchFacetName` errors do not leak field information i.e. fields which are not part of `displayedAttributes` in the settings are hidden from the error message. ## PR checklist Please check if your PR fulfills the following requirements: - [x] Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)? - [x] Have you read the contributing guidelines? - [x] Have you made sure that the title is accurate and descriptive of the changes? Thank you so much for contributing to Meilisearch! Co-authored-by: Vivek Kumar <vivek.26@outlook.com>
This commit is contained in:
commit
914b125c5f
@ -122,22 +122,28 @@ only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and undersco
|
|||||||
.field,
|
.field,
|
||||||
match .valid_fields.is_empty() {
|
match .valid_fields.is_empty() {
|
||||||
true => "This index does not have configured sortable attributes.".to_string(),
|
true => "This index does not have configured sortable attributes.".to_string(),
|
||||||
false => format!("Available sortable attributes are: `{}`.",
|
false => format!("Available sortable attributes are: `{}{}`.",
|
||||||
valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")
|
valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
|
||||||
|
.hidden_fields.then_some(", <..hidden-attributes>").unwrap_or(""),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)]
|
)]
|
||||||
InvalidSortableAttribute { field: String, valid_fields: BTreeSet<String> },
|
InvalidSortableAttribute { field: String, valid_fields: BTreeSet<String>, hidden_fields: bool },
|
||||||
#[error("Attribute `{}` is not facet-searchable. {}",
|
#[error("Attribute `{}` is not facet-searchable. {}",
|
||||||
.field,
|
.field,
|
||||||
match .valid_fields.is_empty() {
|
match .valid_fields.is_empty() {
|
||||||
true => "This index does not have configured facet-searchable attributes. To make it facet-searchable add it to the `filterableAttributes` index settings.".to_string(),
|
true => "This index does not have configured facet-searchable attributes. To make it facet-searchable add it to the `filterableAttributes` index settings.".to_string(),
|
||||||
false => format!("Available facet-searchable attributes are: `{}`. To make it facet-searchable add it to the `filterableAttributes` index settings.",
|
false => format!("Available facet-searchable attributes are: `{}{}`. To make it facet-searchable add it to the `filterableAttributes` index settings.",
|
||||||
valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")
|
valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
|
||||||
|
.hidden_fields.then_some(", <..hidden-attributes>").unwrap_or(""),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)]
|
)]
|
||||||
InvalidFacetSearchFacetName { field: String, valid_fields: BTreeSet<String> },
|
InvalidFacetSearchFacetName {
|
||||||
|
field: String,
|
||||||
|
valid_fields: BTreeSet<String>,
|
||||||
|
hidden_fields: bool,
|
||||||
|
},
|
||||||
#[error("Attribute `{}` is not searchable. Available searchable attributes are: `{}{}`.",
|
#[error("Attribute `{}` is not searchable. Available searchable attributes are: `{}{}`.",
|
||||||
.field,
|
.field,
|
||||||
.valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
|
.valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "),
|
||||||
@ -340,8 +346,11 @@ fn conditionally_lookup_for_error_message() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (list, suffix) in messages {
|
for (list, suffix) in messages {
|
||||||
let err =
|
let err = UserError::InvalidSortableAttribute {
|
||||||
UserError::InvalidSortableAttribute { field: "name".to_string(), valid_fields: list };
|
field: "name".to_string(),
|
||||||
|
valid_fields: list,
|
||||||
|
hidden_fields: false,
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(err.to_string(), format!("{} {}", prefix, suffix));
|
assert_eq!(err.to_string(), format!("{} {}", prefix, suffix));
|
||||||
}
|
}
|
||||||
|
@ -655,6 +655,26 @@ impl Index {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* remove hidden fields */
|
||||||
|
pub fn remove_hidden_fields(
|
||||||
|
&self,
|
||||||
|
rtxn: &RoTxn,
|
||||||
|
fields: impl IntoIterator<Item = impl AsRef<str>>,
|
||||||
|
) -> Result<(BTreeSet<String>, bool)> {
|
||||||
|
let mut valid_fields =
|
||||||
|
fields.into_iter().map(|f| f.as_ref().to_string()).collect::<BTreeSet<String>>();
|
||||||
|
|
||||||
|
let fields_len = valid_fields.len();
|
||||||
|
|
||||||
|
if let Some(dn) = self.displayed_fields(rtxn)? {
|
||||||
|
let displayable_names = dn.iter().map(|s| s.to_string()).collect();
|
||||||
|
valid_fields = &valid_fields & &displayable_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hidden_fields = fields_len > valid_fields.len();
|
||||||
|
Ok((valid_fields, hidden_fields))
|
||||||
|
}
|
||||||
|
|
||||||
/* searchable fields */
|
/* searchable fields */
|
||||||
|
|
||||||
/// Write the user defined searchable fields and generate the real searchable fields from the specified fields ids map.
|
/// Write the user defined searchable fields and generate the real searchable fields from the specified fields ids map.
|
||||||
|
@ -280,9 +280,13 @@ impl<'a> SearchForFacetValues<'a> {
|
|||||||
|
|
||||||
let filterable_fields = index.filterable_fields(rtxn)?;
|
let filterable_fields = index.filterable_fields(rtxn)?;
|
||||||
if !filterable_fields.contains(&self.facet) {
|
if !filterable_fields.contains(&self.facet) {
|
||||||
|
let (valid_fields, hidden_fields) =
|
||||||
|
index.remove_hidden_fields(rtxn, filterable_fields)?;
|
||||||
|
|
||||||
return Err(UserError::InvalidFacetSearchFacetName {
|
return Err(UserError::InvalidFacetSearchFacetName {
|
||||||
field: self.facet.clone(),
|
field: self.facet.clone(),
|
||||||
valid_fields: filterable_fields.into_iter().collect(),
|
valid_fields,
|
||||||
|
hidden_fields,
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ mod sort;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use std::collections::{BTreeSet, HashSet};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use bucket_sort::{bucket_sort, BucketSortOutput};
|
use bucket_sort::{bucket_sort, BucketSortOutput};
|
||||||
use charabia::TokenizerBuilder;
|
use charabia::TokenizerBuilder;
|
||||||
@ -108,24 +108,11 @@ impl<'ctx> SearchContext<'ctx> {
|
|||||||
(None, None) => continue,
|
(None, None) => continue,
|
||||||
// The field is not searchable => User error
|
// The field is not searchable => User error
|
||||||
(_fid, Some(false)) => {
|
(_fid, Some(false)) => {
|
||||||
let mut valid_fields: BTreeSet<_> =
|
let (valid_fields, hidden_fields) = match searchable_names {
|
||||||
fids_map.names().map(String::from).collect();
|
Some(sn) => self.index.remove_hidden_fields(self.txn, sn)?,
|
||||||
|
None => self.index.remove_hidden_fields(self.txn, fids_map.names())?,
|
||||||
|
};
|
||||||
|
|
||||||
// Filter by the searchable names
|
|
||||||
if let Some(sn) = searchable_names {
|
|
||||||
let searchable_names = sn.iter().map(|s| s.to_string()).collect();
|
|
||||||
valid_fields = &valid_fields & &searchable_names;
|
|
||||||
}
|
|
||||||
|
|
||||||
let searchable_count = valid_fields.len();
|
|
||||||
|
|
||||||
// Remove hidden fields
|
|
||||||
if let Some(dn) = self.index.displayed_fields(self.txn)? {
|
|
||||||
let displayable_names = dn.iter().map(|s| s.to_string()).collect();
|
|
||||||
valid_fields = &valid_fields & &displayable_names;
|
|
||||||
}
|
|
||||||
|
|
||||||
let hidden_fields = searchable_count > valid_fields.len();
|
|
||||||
let field = field_name.to_string();
|
let field = field_name.to_string();
|
||||||
return Err(UserError::InvalidSearchableAttribute {
|
return Err(UserError::InvalidSearchableAttribute {
|
||||||
field,
|
field,
|
||||||
@ -604,16 +591,24 @@ fn check_sort_criteria(ctx: &SearchContext, sort_criteria: Option<&Vec<AscDesc>>
|
|||||||
for asc_desc in sort_criteria {
|
for asc_desc in sort_criteria {
|
||||||
match asc_desc.member() {
|
match asc_desc.member() {
|
||||||
Member::Field(ref field) if !crate::is_faceted(field, &sortable_fields) => {
|
Member::Field(ref field) if !crate::is_faceted(field, &sortable_fields) => {
|
||||||
|
let (valid_fields, hidden_fields) =
|
||||||
|
ctx.index.remove_hidden_fields(ctx.txn, sortable_fields)?;
|
||||||
|
|
||||||
return Err(UserError::InvalidSortableAttribute {
|
return Err(UserError::InvalidSortableAttribute {
|
||||||
field: field.to_string(),
|
field: field.to_string(),
|
||||||
valid_fields: sortable_fields.into_iter().collect(),
|
valid_fields,
|
||||||
})?
|
hidden_fields,
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
Member::Geo(_) if !sortable_fields.contains("_geo") => {
|
Member::Geo(_) if !sortable_fields.contains("_geo") => {
|
||||||
|
let (valid_fields, hidden_fields) =
|
||||||
|
ctx.index.remove_hidden_fields(ctx.txn, sortable_fields)?;
|
||||||
|
|
||||||
return Err(UserError::InvalidSortableAttribute {
|
return Err(UserError::InvalidSortableAttribute {
|
||||||
field: "_geo".to_string(),
|
field: "_geo".to_string(),
|
||||||
valid_fields: sortable_fields.into_iter().collect(),
|
valid_fields,
|
||||||
})?
|
hidden_fields,
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user