mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-11-04 09:56:28 +00: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:
		@@ -122,22 +122,28 @@ only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and undersco
 | 
			
		||||
        .field,
 | 
			
		||||
        match .valid_fields.is_empty() {
 | 
			
		||||
            true => "This index does not have configured sortable attributes.".to_string(),
 | 
			
		||||
            false => format!("Available sortable attributes are: `{}`.",
 | 
			
		||||
                    valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ")
 | 
			
		||||
            false => format!("Available sortable attributes are: `{}{}`.",
 | 
			
		||||
                    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. {}",
 | 
			
		||||
        .field,
 | 
			
		||||
        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(),
 | 
			
		||||
            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(", ")
 | 
			
		||||
            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(", "),
 | 
			
		||||
                    .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: `{}{}`.",
 | 
			
		||||
        .field,
 | 
			
		||||
        .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 {
 | 
			
		||||
        let err =
 | 
			
		||||
            UserError::InvalidSortableAttribute { field: "name".to_string(), valid_fields: list };
 | 
			
		||||
        let err = UserError::InvalidSortableAttribute {
 | 
			
		||||
            field: "name".to_string(),
 | 
			
		||||
            valid_fields: list,
 | 
			
		||||
            hidden_fields: false,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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 */
 | 
			
		||||
 | 
			
		||||
    /// 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)?;
 | 
			
		||||
        if !filterable_fields.contains(&self.facet) {
 | 
			
		||||
            let (valid_fields, hidden_fields) =
 | 
			
		||||
                index.remove_hidden_fields(rtxn, filterable_fields)?;
 | 
			
		||||
 | 
			
		||||
            return Err(UserError::InvalidFacetSearchFacetName {
 | 
			
		||||
                field: self.facet.clone(),
 | 
			
		||||
                valid_fields: filterable_fields.into_iter().collect(),
 | 
			
		||||
                valid_fields,
 | 
			
		||||
                hidden_fields,
 | 
			
		||||
            }
 | 
			
		||||
            .into());
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ mod sort;
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests;
 | 
			
		||||
 | 
			
		||||
use std::collections::{BTreeSet, HashSet};
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
use bucket_sort::{bucket_sort, BucketSortOutput};
 | 
			
		||||
use charabia::TokenizerBuilder;
 | 
			
		||||
@@ -108,24 +108,11 @@ impl<'ctx> SearchContext<'ctx> {
 | 
			
		||||
                (None, None) => continue,
 | 
			
		||||
                // The field is not searchable => User error
 | 
			
		||||
                (_fid, Some(false)) => {
 | 
			
		||||
                    let mut valid_fields: BTreeSet<_> =
 | 
			
		||||
                        fids_map.names().map(String::from).collect();
 | 
			
		||||
                    let (valid_fields, hidden_fields) = match searchable_names {
 | 
			
		||||
                        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();
 | 
			
		||||
                    return Err(UserError::InvalidSearchableAttribute {
 | 
			
		||||
                        field,
 | 
			
		||||
@@ -604,16 +591,24 @@ fn check_sort_criteria(ctx: &SearchContext, sort_criteria: Option<&Vec<AscDesc>>
 | 
			
		||||
    for asc_desc in sort_criteria {
 | 
			
		||||
        match asc_desc.member() {
 | 
			
		||||
            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 {
 | 
			
		||||
                    field: field.to_string(),
 | 
			
		||||
                    valid_fields: sortable_fields.into_iter().collect(),
 | 
			
		||||
                })?
 | 
			
		||||
                    valid_fields,
 | 
			
		||||
                    hidden_fields,
 | 
			
		||||
                })?;
 | 
			
		||||
            }
 | 
			
		||||
            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 {
 | 
			
		||||
                    field: "_geo".to_string(),
 | 
			
		||||
                    valid_fields: sortable_fields.into_iter().collect(),
 | 
			
		||||
                })?
 | 
			
		||||
                    valid_fields,
 | 
			
		||||
                    hidden_fields,
 | 
			
		||||
                })?;
 | 
			
		||||
            }
 | 
			
		||||
            _ => (),
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user