diff --git a/Cargo.lock b/Cargo.lock index cef8e9c8a..c2b3a673e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3507,6 +3507,7 @@ version = "1.11.0" dependencies = [ "actix-web", "anyhow", + "bitflags 2.6.0", "convert_case 0.6.0", "csv", "deserr", diff --git a/crates/meilisearch-auth/src/store.rs b/crates/meilisearch-auth/src/store.rs index ef992e836..62aa445fa 100644 --- a/crates/meilisearch-auth/src/store.rs +++ b/crates/meilisearch-auth/src/store.rs @@ -105,7 +105,7 @@ impl HeedAuthStore { let mut actions = HashSet::new(); for action in &key.actions { - match action { + match *action { Action::All => actions.extend(enum_iterator::all::()), Action::DocumentsAll => { actions.extend( @@ -128,23 +128,11 @@ impl HeedAuthStore { Action::SettingsAll => { actions.extend([Action::SettingsGet, Action::SettingsUpdate].iter()); } - Action::DumpsAll => { - actions.insert(Action::DumpsCreate); - } - Action::SnapshotsAll => { - actions.insert(Action::SnapshotsCreate); - } Action::TasksAll => { actions.extend([Action::TasksGet, Action::TasksDelete, Action::TasksCancel]); } - Action::StatsAll => { - actions.insert(Action::StatsGet); - } - Action::MetricsAll => { - actions.insert(Action::MetricsGet); - } other => { - actions.insert(*other); + actions.insert(other); } } } @@ -293,18 +281,24 @@ impl HeedAuthStore { /// optionally on a specific index, for a given key. pub struct KeyIdActionCodec; +impl KeyIdActionCodec { + fn action_parts_to_32bits([p1, p2, p3, p4]: &[u8; 4]) -> u32 { + ((*p1 as u32) << 24) | ((*p2 as u32) << 16) | ((*p3 as u32) << 8) | (*p4 as u32) + } +} + impl<'a> milli::heed::BytesDecode<'a> for KeyIdActionCodec { type DItem = (KeyId, Action, Option<&'a [u8]>); fn bytes_decode(bytes: &'a [u8]) -> StdResult { let (key_id_bytes, action_bytes) = try_split_array_at(bytes).ok_or(SliceTooShortError)?; - let (&action_byte, index) = - match try_split_array_at(action_bytes).ok_or(SliceTooShortError)? { - ([action], []) => (action, None), - ([action], index) => (action, Some(index)), + let (action_bits, index) = + match try_split_array_at::(action_bytes).ok_or(SliceTooShortError)? { + (action_parts, []) => (Self::action_parts_to_32bits(action_parts), None), + (action_parts, index) => (Self::action_parts_to_32bits(action_parts), Some(index)), }; let key_id = Uuid::from_bytes(*key_id_bytes); - let action = Action::from_repr(action_byte).ok_or(InvalidActionError { action_byte })?; + let action = Action::from_bits(action_bits).ok_or(InvalidActionError { action_bits })?; Ok((key_id, action, index)) } @@ -317,7 +311,7 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec { let mut bytes = Vec::new(); bytes.extend_from_slice(key_id.as_bytes()); - let action_bytes = u8::to_be_bytes(action.repr()); + let action_bytes = u32::to_be_bytes(action.bits()); bytes.extend_from_slice(&action_bytes); if let Some(index) = index { bytes.extend_from_slice(index); @@ -332,9 +326,9 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec { pub struct SliceTooShortError; #[derive(Error, Debug)] -#[error("cannot construct a valid Action from {action_byte}")] +#[error("cannot construct a valid Action from {action_bits}")] pub struct InvalidActionError { - pub action_byte: u8, + pub action_bits: u32, } pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String { diff --git a/crates/meilisearch-types/Cargo.toml b/crates/meilisearch-types/Cargo.toml index 0dae024f2..de42e5586 100644 --- a/crates/meilisearch-types/Cargo.toml +++ b/crates/meilisearch-types/Cargo.toml @@ -38,6 +38,7 @@ time = { version = "0.3.36", features = [ ] } tokio = "1.38" uuid = { version = "1.10.0", features = ["serde", "v4"] } +bitflags = "2.6.0" [dev-dependencies] insta = "1.39.0" diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index f7d80bbcb..436854d7d 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -2,10 +2,11 @@ use std::convert::Infallible; use std::hash::Hash; use std::str::FromStr; -use deserr::{DeserializeError, Deserr, MergeWithError, ValuePointerRef}; +use bitflags::{bitflags, Flags}; +use deserr::{take_cf_content, DeserializeError, Deserr, MergeWithError, ValuePointerRef}; use enum_iterator::Sequence; use milli::update::Setting; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use time::format_description::well_known::Rfc3339; use time::macros::{format_description, time}; use time::{Date, OffsetDateTime, PrimitiveDateTime}; @@ -179,193 +180,284 @@ fn parse_expiration_date( } } -#[derive(Copy, Clone, Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Sequence, Deserr)] -#[repr(u8)] -pub enum Action { - #[serde(rename = "*")] - #[deserr(rename = "*")] - All = 0, - #[serde(rename = "search")] - #[deserr(rename = "search")] - Search, - #[serde(rename = "documents.*")] - #[deserr(rename = "documents.*")] - DocumentsAll, - #[serde(rename = "documents.add")] - #[deserr(rename = "documents.add")] - DocumentsAdd, - #[serde(rename = "documents.get")] - #[deserr(rename = "documents.get")] - DocumentsGet, - #[serde(rename = "documents.delete")] - #[deserr(rename = "documents.delete")] - DocumentsDelete, - #[serde(rename = "indexes.*")] - #[deserr(rename = "indexes.*")] - IndexesAll, - #[serde(rename = "indexes.create")] - #[deserr(rename = "indexes.create")] - IndexesAdd, - #[serde(rename = "indexes.get")] - #[deserr(rename = "indexes.get")] - IndexesGet, - #[serde(rename = "indexes.update")] - #[deserr(rename = "indexes.update")] - IndexesUpdate, - #[serde(rename = "indexes.delete")] - #[deserr(rename = "indexes.delete")] - IndexesDelete, - #[serde(rename = "indexes.swap")] - #[deserr(rename = "indexes.swap")] - IndexesSwap, - #[serde(rename = "tasks.*")] - #[deserr(rename = "tasks.*")] - TasksAll, - #[serde(rename = "tasks.cancel")] - #[deserr(rename = "tasks.cancel")] - TasksCancel, - #[serde(rename = "tasks.delete")] - #[deserr(rename = "tasks.delete")] - TasksDelete, - #[serde(rename = "tasks.get")] - #[deserr(rename = "tasks.get")] - TasksGet, - #[serde(rename = "settings.*")] - #[deserr(rename = "settings.*")] - SettingsAll, - #[serde(rename = "settings.get")] - #[deserr(rename = "settings.get")] - SettingsGet, - #[serde(rename = "settings.update")] - #[deserr(rename = "settings.update")] - SettingsUpdate, - #[serde(rename = "stats.*")] - #[deserr(rename = "stats.*")] - StatsAll, - #[serde(rename = "stats.get")] - #[deserr(rename = "stats.get")] - StatsGet, - #[serde(rename = "metrics.*")] - #[deserr(rename = "metrics.*")] - MetricsAll, - #[serde(rename = "metrics.get")] - #[deserr(rename = "metrics.get")] - MetricsGet, - #[serde(rename = "dumps.*")] - #[deserr(rename = "dumps.*")] - DumpsAll, - #[serde(rename = "dumps.create")] - #[deserr(rename = "dumps.create")] - DumpsCreate, - #[serde(rename = "snapshots.*")] - #[deserr(rename = "snapshots.*")] - SnapshotsAll, - #[serde(rename = "snapshots.create")] - #[deserr(rename = "snapshots.create")] - SnapshotsCreate, - #[serde(rename = "version")] - #[deserr(rename = "version")] - Version, - #[serde(rename = "keys.create")] - #[deserr(rename = "keys.create")] - KeysAdd, - #[serde(rename = "keys.get")] - #[deserr(rename = "keys.get")] - KeysGet, - #[serde(rename = "keys.update")] - #[deserr(rename = "keys.update")] - KeysUpdate, - #[serde(rename = "keys.delete")] - #[deserr(rename = "keys.delete")] - KeysDelete, - #[serde(rename = "experimental.get")] - #[deserr(rename = "experimental.get")] - ExperimentalFeaturesGet, - #[serde(rename = "experimental.update")] - #[deserr(rename = "experimental.update")] - ExperimentalFeaturesUpdate, +bitflags! { + #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] + #[repr(transparent)] + // NOTE: For `Sequence` impl to work, the values of these must be in ascending order + pub struct Action: u32 { + const Search = 1; + // Documents + const DocumentsAdd = 1 << 1; + const DocumentsGet = 1 << 2; + const DocumentsDelete = 1 << 3; + const DocumentsAll = Self::DocumentsAdd.bits() | Self::DocumentsGet.bits() | Self::DocumentsDelete.bits(); + // Indexes + const IndexesAdd = 1 << 4; + const IndexesGet = 1 << 5; + const IndexesUpdate = 1 << 6; + const IndexesDelete = 1 << 7; + const IndexesSwap = 1 << 8; + const IndexesAll = Self::IndexesAdd.bits() | Self::IndexesGet.bits() | Self::IndexesUpdate.bits() | Self::IndexesDelete.bits() | Self::IndexesSwap.bits(); + // Tasks + const TasksCancel = 1 << 9; + const TasksDelete = 1 << 10; + const TasksGet = 1 << 11; + const TasksAll = Self::TasksCancel.bits() | Self::TasksDelete.bits() | Self::TasksGet.bits(); + // Settings + const SettingsGet = 1 << 12; + const SettingsUpdate = 1 << 13; + const SettingsAll = Self::SettingsGet.bits() | Self::SettingsUpdate.bits(); + // Stats + const StatsGet = 1 << 14; + const StatsAll = Self::StatsGet.bits(); + // Metrics + const MetricsGet = 1 << 15; + const MetricsAll = Self::MetricsGet.bits(); + // Dumps + const DumpsCreate = 1 << 16; + const DumpsAll = Self::DumpsCreate.bits(); + // Snapshots + const SnapshotsCreate = 1 << 17; + const SnapshotsAll = Self::SnapshotsCreate.bits(); + // Keys without an "all" version + const Version = 1 << 18; + const KeysAdd = 1 << 19; + const KeysGet = 1 << 20; + const KeysUpdate = 1 << 21; + const KeysDelete = 1 << 22; + const ExperimentalFeaturesGet = 1 << 23; + const ExperimentalFeaturesUpdate = 1 << 24; + // All + const All = 0xFFFFFFFF >> (32 - 1 - 24); + } } impl Action { - pub const fn from_repr(repr: u8) -> Option { - use actions::*; - match repr { - ALL => Some(Self::All), - SEARCH => Some(Self::Search), - DOCUMENTS_ALL => Some(Self::DocumentsAll), - DOCUMENTS_ADD => Some(Self::DocumentsAdd), - DOCUMENTS_GET => Some(Self::DocumentsGet), - DOCUMENTS_DELETE => Some(Self::DocumentsDelete), - INDEXES_ALL => Some(Self::IndexesAll), - INDEXES_CREATE => Some(Self::IndexesAdd), - INDEXES_GET => Some(Self::IndexesGet), - INDEXES_UPDATE => Some(Self::IndexesUpdate), - INDEXES_DELETE => Some(Self::IndexesDelete), - INDEXES_SWAP => Some(Self::IndexesSwap), - TASKS_ALL => Some(Self::TasksAll), - TASKS_CANCEL => Some(Self::TasksCancel), - TASKS_DELETE => Some(Self::TasksDelete), - TASKS_GET => Some(Self::TasksGet), - SETTINGS_ALL => Some(Self::SettingsAll), - SETTINGS_GET => Some(Self::SettingsGet), - SETTINGS_UPDATE => Some(Self::SettingsUpdate), - STATS_ALL => Some(Self::StatsAll), - STATS_GET => Some(Self::StatsGet), - METRICS_ALL => Some(Self::MetricsAll), - METRICS_GET => Some(Self::MetricsGet), - DUMPS_ALL => Some(Self::DumpsAll), - DUMPS_CREATE => Some(Self::DumpsCreate), - SNAPSHOTS_CREATE => Some(Self::SnapshotsCreate), - VERSION => Some(Self::Version), - KEYS_CREATE => Some(Self::KeysAdd), - KEYS_GET => Some(Self::KeysGet), - KEYS_UPDATE => Some(Self::KeysUpdate), - KEYS_DELETE => Some(Self::KeysDelete), - EXPERIMENTAL_FEATURES_GET => Some(Self::ExperimentalFeaturesGet), - EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), - _otherwise => None, - } + const SERDE_MAP_ARR: [(&'static str, Self); 34] = [ + ("search", Self::Search), + ("documents.add", Self::DocumentsAdd), + ("documents.get", Self::DocumentsGet), + ("documents.delete", Self::DocumentsDelete), + ("documents.*", Self::DocumentsAll), + ("indexes.create", Self::IndexesAdd), + ("indexes.get", Self::IndexesGet), + ("indexes.update", Self::IndexesUpdate), + ("indexes.delete", Self::IndexesDelete), + ("indexes.swap", Self::IndexesSwap), + ("indexes.*", Self::IndexesAll), + ("tasks.cancel", Self::TasksCancel), + ("tasks.delete", Self::TasksDelete), + ("tasks.get", Self::TasksGet), + ("tasks.*", Self::TasksAll), + ("settings.get", Self::SettingsGet), + ("settings.update", Self::SettingsUpdate), + ("settings.*", Self::SettingsAll), + ("stats.get", Self::StatsGet), + ("stats.*", Self::StatsAll), + ("metrics.get", Self::MetricsGet), + ("metrics.*", Self::MetricsAll), + ("dumps.create", Self::DumpsCreate), + ("dumps.*", Self::DumpsAll), + ("snapshots.create", Self::SnapshotsCreate), + ("snapshots.*", Self::SnapshotsAll), + ("version", Self::Version), + ("keys.create", Self::KeysAdd), + ("keys.get", Self::KeysGet), + ("keys.update", Self::KeysUpdate), + ("keys.delete", Self::KeysDelete), + ("experimental.get", Self::ExperimentalFeaturesGet), + ("experimental.update", Self::ExperimentalFeaturesUpdate), + ("*", Self::All), + ]; + + fn get_action(v: &str) -> Option { + Self::SERDE_MAP_ARR + .iter() + .find(|(serde_name, _)| &v == serde_name) + .map(|(_, action)| *action) } - pub const fn repr(&self) -> u8 { - *self as u8 + fn get_action_serde_name(v: &Action) -> &'static str { + Self::SERDE_MAP_ARR + .iter() + .find(|(_, action)| v == action) + .map(|(serde_name, _)| serde_name) + .expect("an action is missing a matching serialized value") + } + + // when we remove "all" flags, this will give us the exact index + fn get_potential_index(&self) -> usize { + if self.is_empty() { + return 0; + } + + // most significant bit for u32 + let msb = 1u32 << (31 - self.bits().leading_zeros()); + + // index of the single set bit + msb.trailing_zeros() as usize } } pub mod actions { - use super::Action::*; + use super::Action as A; - pub(crate) const ALL: u8 = All.repr(); - pub const SEARCH: u8 = Search.repr(); - pub const DOCUMENTS_ALL: u8 = DocumentsAll.repr(); - pub const DOCUMENTS_ADD: u8 = DocumentsAdd.repr(); - pub const DOCUMENTS_GET: u8 = DocumentsGet.repr(); - pub const DOCUMENTS_DELETE: u8 = DocumentsDelete.repr(); - pub const INDEXES_ALL: u8 = IndexesAll.repr(); - pub const INDEXES_CREATE: u8 = IndexesAdd.repr(); - pub const INDEXES_GET: u8 = IndexesGet.repr(); - pub const INDEXES_UPDATE: u8 = IndexesUpdate.repr(); - pub const INDEXES_DELETE: u8 = IndexesDelete.repr(); - pub const INDEXES_SWAP: u8 = IndexesSwap.repr(); - pub const TASKS_ALL: u8 = TasksAll.repr(); - pub const TASKS_CANCEL: u8 = TasksCancel.repr(); - pub const TASKS_DELETE: u8 = TasksDelete.repr(); - pub const TASKS_GET: u8 = TasksGet.repr(); - pub const SETTINGS_ALL: u8 = SettingsAll.repr(); - pub const SETTINGS_GET: u8 = SettingsGet.repr(); - pub const SETTINGS_UPDATE: u8 = SettingsUpdate.repr(); - pub const STATS_ALL: u8 = StatsAll.repr(); - pub const STATS_GET: u8 = StatsGet.repr(); - pub const METRICS_ALL: u8 = MetricsAll.repr(); - pub const METRICS_GET: u8 = MetricsGet.repr(); - pub const DUMPS_ALL: u8 = DumpsAll.repr(); - pub const DUMPS_CREATE: u8 = DumpsCreate.repr(); - pub const SNAPSHOTS_CREATE: u8 = SnapshotsCreate.repr(); - pub const VERSION: u8 = Version.repr(); - pub const KEYS_CREATE: u8 = KeysAdd.repr(); - pub const KEYS_GET: u8 = KeysGet.repr(); - pub const KEYS_UPDATE: u8 = KeysUpdate.repr(); - pub const KEYS_DELETE: u8 = KeysDelete.repr(); - pub const EXPERIMENTAL_FEATURES_GET: u8 = ExperimentalFeaturesGet.repr(); - pub const EXPERIMENTAL_FEATURES_UPDATE: u8 = ExperimentalFeaturesUpdate.repr(); + pub const SEARCH: u32 = A::Search.bits(); + pub const DOCUMENTS_ADD: u32 = A::DocumentsAdd.bits(); + pub const DOCUMENTS_GET: u32 = A::DocumentsGet.bits(); + pub const DOCUMENTS_DELETE: u32 = A::DocumentsDelete.bits(); + pub const DOCUMENTS_ALL: u32 = A::DocumentsAll.bits(); + pub const INDEXES_CREATE: u32 = A::IndexesAdd.bits(); + pub const INDEXES_GET: u32 = A::IndexesGet.bits(); + pub const INDEXES_UPDATE: u32 = A::IndexesUpdate.bits(); + pub const INDEXES_DELETE: u32 = A::IndexesDelete.bits(); + pub const INDEXES_SWAP: u32 = A::IndexesSwap.bits(); + pub const INDEXES_ALL: u32 = A::IndexesAll.bits(); + pub const TASKS_CANCEL: u32 = A::TasksCancel.bits(); + pub const TASKS_DELETE: u32 = A::TasksDelete.bits(); + pub const TASKS_GET: u32 = A::TasksGet.bits(); + pub const TASKS_ALL: u32 = A::TasksAll.bits(); + pub const SETTINGS_GET: u32 = A::SettingsGet.bits(); + pub const SETTINGS_UPDATE: u32 = A::SettingsUpdate.bits(); + pub const SETTINGS_ALL: u32 = A::SettingsAll.bits(); + pub const STATS_GET: u32 = A::StatsGet.bits(); + pub const STATS_ALL: u32 = A::StatsAll.bits(); + pub const METRICS_GET: u32 = A::MetricsGet.bits(); + pub const METRICS_ALL: u32 = A::MetricsAll.bits(); + pub const DUMPS_CREATE: u32 = A::DumpsCreate.bits(); + pub const DUMPS_ALL: u32 = A::DumpsAll.bits(); + pub const SNAPSHOTS_CREATE: u32 = A::SnapshotsCreate.bits(); + pub const SNAPSHOTS_ALL: u32 = A::SnapshotsAll.bits(); + pub const VERSION: u32 = A::Version.bits(); + pub const KEYS_CREATE: u32 = A::KeysAdd.bits(); + pub const KEYS_GET: u32 = A::KeysGet.bits(); + pub const KEYS_UPDATE: u32 = A::KeysUpdate.bits(); + pub const KEYS_DELETE: u32 = A::KeysDelete.bits(); + pub const EXPERIMENTAL_FEATURES_GET: u32 = A::ExperimentalFeaturesGet.bits(); + pub const EXPERIMENTAL_FEATURES_UPDATE: u32 = A::ExperimentalFeaturesUpdate.bits(); + pub const ALL: u32 = A::All.bits(); +} + +impl Deserr for Action { + fn deserialize_from_value( + value: deserr::Value, + location: deserr::ValuePointerRef<'_>, + ) -> Result { + match value { + deserr::Value::String(s) => match Self::get_action(&s) { + Some(action) => Ok(action), + None => Err(deserr::take_cf_content(E::error::( + None, + deserr::ErrorKind::UnknownValue { + value: &s, + accepted: &Self::SERDE_MAP_ARR.map(|(ser_action, _)| ser_action), + }, + location, + ))), + }, + _ => Err(take_cf_content(E::error( + None, + deserr::ErrorKind::IncorrectValueKind { + actual: value, + accepted: &[deserr::ValueKind::String], + }, + location, + ))), + } + } +} + +impl Serialize for Action { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(Self::get_action_serde_name(self)) + } +} + +impl<'de> Deserialize<'de> for Action { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct Visitor; + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = Action; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "the name of a valid action (string)") + } + + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + match Self::Value::get_action(s) { + Some(action) => Ok(action), + None => Err(E::invalid_value(serde::de::Unexpected::Str(s), &"a valid action")), + } + } + } + + deserializer.deserialize_str(Visitor) + } +} + +// TODO: Once "all" type flags are removed, simplify +// Essentially `get_potential_index` will give the exact index, +1 the exact next, -1 the exact previous +impl Sequence for Action { + const CARDINALITY: usize = Self::FLAGS.len(); + + fn next(&self) -> Option { + let mut potential_next_index = self.get_potential_index() + 1; + + loop { + if let Some(next_flag) = Self::FLAGS.get(potential_next_index).map(|v| v.value()) { + if next_flag > self { + return Some(*next_flag); + } + + potential_next_index += 1; + } else { + return None; + } + } + } + + fn previous(&self) -> Option { + // -2 because of "all" type flags that represent a single flag, otherwise -1 would suffice + let initial_potential_index = self.get_potential_index(); + if initial_potential_index == 0 { + return None; + } + + let mut potential_previous_index: usize = + if initial_potential_index == 1 { 0 } else { initial_potential_index - 2 }; + + let mut previous_item: Option = None; + let mut pre_previous_item: Option = None; + + loop { + if let Some(next_flag) = Self::FLAGS.get(potential_previous_index).map(|v| v.value()) { + if next_flag > self { + return pre_previous_item; + } + + pre_previous_item = previous_item; + previous_item = Some(*next_flag); + potential_previous_index += 1; + } else { + return pre_previous_item; + } + } + } + + fn first() -> Option { + Self::FLAGS.first().map(|v| *v.value()) + } + + fn last() -> Option { + Self::FLAGS.last().map(|v| *v.value()) + } } diff --git a/crates/meilisearch/src/extractors/authentication/mod.rs b/crates/meilisearch/src/extractors/authentication/mod.rs index 28a6d770e..e10ad2bd1 100644 --- a/crates/meilisearch/src/extractors/authentication/mod.rs +++ b/crates/meilisearch/src/extractors/authentication/mod.rs @@ -171,7 +171,7 @@ pub mod policies { #[error("Could not decode tenant token, {0}.")] CouldNotDecodeTenantToken(jsonwebtoken::errors::Error), #[error("Invalid action `{0}`.")] - InternalInvalidAction(u8), + InternalInvalidAction(u32), } impl From for AuthError { @@ -214,14 +214,14 @@ pub mod policies { Ok(api_key_uid) } - fn is_keys_action(action: u8) -> bool { + fn is_keys_action(action: u32) -> bool { use actions::*; matches!(action, KEYS_GET | KEYS_CREATE | KEYS_UPDATE | KEYS_DELETE) } - pub struct ActionPolicy; + pub struct ActionPolicy; - impl Policy for ActionPolicy { + impl Policy for ActionPolicy { /// Attempts to grant authentication from a bearer token (that can be a tenant token or an API key), the requested Action, /// and a list of requested indexes. /// @@ -255,7 +255,7 @@ pub mod policies { }; // check that the indexes are allowed - let action = Action::from_repr(A).ok_or(AuthError::InternalInvalidAction(A))?; + let action = Action::from_bits(A).ok_or(AuthError::InternalInvalidAction(A))?; let auth_filter = auth .get_key_filters(key_uuid, search_rules) .map_err(|_e| AuthError::InvalidApiKey)?; @@ -294,7 +294,7 @@ pub mod policies { } } - impl ActionPolicy { + impl ActionPolicy { fn authenticate_tenant_token( auth: &AuthController, token: &str, diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index 253929428..a465b022a 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -421,7 +421,7 @@ async fn error_add_api_key_invalid_parameters_actions() { meili_snap::snapshot!(code, @"400 Bad Request"); meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###" { - "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`", + "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `search`, `documents.add`, `documents.get`, `documents.delete`, `documents.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `indexes.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `tasks.*`, `settings.get`, `settings.update`, `settings.*`, `stats.get`, `stats.*`, `metrics.get`, `metrics.*`, `dumps.create`, `dumps.*`, `snapshots.create`, `snapshots.*`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `*`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" diff --git a/crates/meilisearch/tests/auth/authorization.rs b/crates/meilisearch/tests/auth/authorization.rs index 609b7d01b..cb2510663 100644 --- a/crates/meilisearch/tests/auth/authorization.rs +++ b/crates/meilisearch/tests/auth/authorization.rs @@ -53,14 +53,14 @@ pub static AUTHORIZATIONS: Lazy hashset!{"settings.update", "settings.*", "*"}, ("PUT", "/indexes/products/settings/stop-words") => hashset!{"settings.update", "settings.*", "*"}, ("PUT", "/indexes/products/settings/synonyms") => hashset!{"settings.update", "settings.*", "*"}, - ("GET", "/indexes/products/stats") => hashset!{"stats.get", "stats.*", "*"}, - ("GET", "/stats") => hashset!{"stats.get", "stats.*", "*"}, - ("POST", "/dumps") => hashset!{"dumps.create", "dumps.*", "*"}, - ("POST", "/snapshots") => hashset!{"snapshots.create", "snapshots.*", "*"}, + ("GET", "/indexes/products/stats") => hashset!{"stats.get", "*"}, + ("GET", "/stats") => hashset!{"stats.get", "*"}, + ("POST", "/dumps") => hashset!{"dumps.create", "*"}, + ("POST", "/snapshots") => hashset!{"snapshots.create", "*"}, ("GET", "/version") => hashset!{"version", "*"}, - ("GET", "/metrics") => hashset!{"metrics.get", "metrics.*", "*"}, - ("POST", "/logs/stream") => hashset!{"metrics.get", "metrics.*", "*"}, - ("DELETE", "/logs/stream") => hashset!{"metrics.get", "metrics.*", "*"}, + ("GET", "/metrics") => hashset!{"metrics.get", "*"}, + ("POST", "/logs/stream") => hashset!{"metrics.get", "*"}, + ("DELETE", "/logs/stream") => hashset!{"metrics.get", "*"}, ("PATCH", "/keys/mykey/") => hashset!{"keys.update", "*"}, ("GET", "/keys/mykey/") => hashset!{"keys.get", "*"}, ("DELETE", "/keys/mykey/") => hashset!{"keys.delete", "*"}, diff --git a/crates/meilisearch/tests/auth/errors.rs b/crates/meilisearch/tests/auth/errors.rs index c063b2aac..4ba9874bc 100644 --- a/crates/meilisearch/tests/auth/errors.rs +++ b/crates/meilisearch/tests/auth/errors.rs @@ -93,7 +93,7 @@ async fn create_api_key_bad_actions() { snapshot!(code, @"400 Bad Request"); snapshot!(json_string!(response), @r###" { - "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`", + "message": "Unknown value `doggo` at `.actions[0]`: expected one of `search`, `documents.add`, `documents.get`, `documents.delete`, `documents.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `indexes.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `tasks.*`, `settings.get`, `settings.update`, `settings.*`, `stats.get`, `stats.*`, `metrics.get`, `metrics.*`, `dumps.create`, `dumps.*`, `snapshots.create`, `snapshots.*`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `*`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"