This commit is contained in:
F. Levi 2024-11-13 13:59:04 +00:00 committed by GitHub
commit 1416ee9acc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 306 additions and 218 deletions

1
Cargo.lock generated
View File

@ -3507,6 +3507,7 @@ version = "1.11.0"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"anyhow", "anyhow",
"bitflags 2.6.0",
"convert_case 0.6.0", "convert_case 0.6.0",
"csv", "csv",
"deserr", "deserr",

View File

@ -105,7 +105,7 @@ impl HeedAuthStore {
let mut actions = HashSet::new(); let mut actions = HashSet::new();
for action in &key.actions { for action in &key.actions {
match action { match *action {
Action::All => actions.extend(enum_iterator::all::<Action>()), Action::All => actions.extend(enum_iterator::all::<Action>()),
Action::DocumentsAll => { Action::DocumentsAll => {
actions.extend( actions.extend(
@ -128,23 +128,11 @@ impl HeedAuthStore {
Action::SettingsAll => { Action::SettingsAll => {
actions.extend([Action::SettingsGet, Action::SettingsUpdate].iter()); actions.extend([Action::SettingsGet, Action::SettingsUpdate].iter());
} }
Action::DumpsAll => {
actions.insert(Action::DumpsCreate);
}
Action::SnapshotsAll => {
actions.insert(Action::SnapshotsCreate);
}
Action::TasksAll => { Action::TasksAll => {
actions.extend([Action::TasksGet, Action::TasksDelete, Action::TasksCancel]); actions.extend([Action::TasksGet, Action::TasksDelete, Action::TasksCancel]);
} }
Action::StatsAll => {
actions.insert(Action::StatsGet);
}
Action::MetricsAll => {
actions.insert(Action::MetricsGet);
}
other => { other => {
actions.insert(*other); actions.insert(other);
} }
} }
} }
@ -293,18 +281,24 @@ impl HeedAuthStore {
/// optionally on a specific index, for a given key. /// optionally on a specific index, for a given key.
pub struct KeyIdActionCodec; 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 { impl<'a> milli::heed::BytesDecode<'a> for KeyIdActionCodec {
type DItem = (KeyId, Action, Option<&'a [u8]>); type DItem = (KeyId, Action, Option<&'a [u8]>);
fn bytes_decode(bytes: &'a [u8]) -> StdResult<Self::DItem, BoxedError> { fn bytes_decode(bytes: &'a [u8]) -> StdResult<Self::DItem, BoxedError> {
let (key_id_bytes, action_bytes) = try_split_array_at(bytes).ok_or(SliceTooShortError)?; let (key_id_bytes, action_bytes) = try_split_array_at(bytes).ok_or(SliceTooShortError)?;
let (&action_byte, index) = let (action_bits, index) =
match try_split_array_at(action_bytes).ok_or(SliceTooShortError)? { match try_split_array_at::<u8, 4>(action_bytes).ok_or(SliceTooShortError)? {
([action], []) => (action, None), (action_parts, []) => (Self::action_parts_to_32bits(action_parts), None),
([action], index) => (action, Some(index)), (action_parts, index) => (Self::action_parts_to_32bits(action_parts), Some(index)),
}; };
let key_id = Uuid::from_bytes(*key_id_bytes); 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)) Ok((key_id, action, index))
} }
@ -317,7 +311,7 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
bytes.extend_from_slice(key_id.as_bytes()); 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); bytes.extend_from_slice(&action_bytes);
if let Some(index) = index { if let Some(index) = index {
bytes.extend_from_slice(index); bytes.extend_from_slice(index);
@ -332,9 +326,9 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec {
pub struct SliceTooShortError; pub struct SliceTooShortError;
#[derive(Error, Debug)] #[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 struct InvalidActionError {
pub action_byte: u8, pub action_bits: u32,
} }
pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String { pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String {

View File

@ -38,6 +38,7 @@ time = { version = "0.3.36", features = [
] } ] }
tokio = "1.38" tokio = "1.38"
uuid = { version = "1.10.0", features = ["serde", "v4"] } uuid = { version = "1.10.0", features = ["serde", "v4"] }
bitflags = "2.6.0"
[dev-dependencies] [dev-dependencies]
insta = "1.39.0" insta = "1.39.0"

View File

@ -2,10 +2,11 @@ use std::convert::Infallible;
use std::hash::Hash; use std::hash::Hash;
use std::str::FromStr; 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 enum_iterator::Sequence;
use milli::update::Setting; use milli::update::Setting;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use time::format_description::well_known::Rfc3339; use time::format_description::well_known::Rfc3339;
use time::macros::{format_description, time}; use time::macros::{format_description, time};
use time::{Date, OffsetDateTime, PrimitiveDateTime}; use time::{Date, OffsetDateTime, PrimitiveDateTime};
@ -179,193 +180,284 @@ fn parse_expiration_date(
} }
} }
#[derive(Copy, Clone, Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Sequence, Deserr)] bitflags! {
#[repr(u8)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub enum Action { #[repr(transparent)]
#[serde(rename = "*")] // NOTE: For `Sequence` impl to work, the values of these must be in ascending order
#[deserr(rename = "*")] pub struct Action: u32 {
All = 0, const Search = 1;
#[serde(rename = "search")] // Documents
#[deserr(rename = "search")] const DocumentsAdd = 1 << 1;
Search, const DocumentsGet = 1 << 2;
#[serde(rename = "documents.*")] const DocumentsDelete = 1 << 3;
#[deserr(rename = "documents.*")] const DocumentsAll = Self::DocumentsAdd.bits() | Self::DocumentsGet.bits() | Self::DocumentsDelete.bits();
DocumentsAll, // Indexes
#[serde(rename = "documents.add")] const IndexesAdd = 1 << 4;
#[deserr(rename = "documents.add")] const IndexesGet = 1 << 5;
DocumentsAdd, const IndexesUpdate = 1 << 6;
#[serde(rename = "documents.get")] const IndexesDelete = 1 << 7;
#[deserr(rename = "documents.get")] const IndexesSwap = 1 << 8;
DocumentsGet, const IndexesAll = Self::IndexesAdd.bits() | Self::IndexesGet.bits() | Self::IndexesUpdate.bits() | Self::IndexesDelete.bits() | Self::IndexesSwap.bits();
#[serde(rename = "documents.delete")] // Tasks
#[deserr(rename = "documents.delete")] const TasksCancel = 1 << 9;
DocumentsDelete, const TasksDelete = 1 << 10;
#[serde(rename = "indexes.*")] const TasksGet = 1 << 11;
#[deserr(rename = "indexes.*")] const TasksAll = Self::TasksCancel.bits() | Self::TasksDelete.bits() | Self::TasksGet.bits();
IndexesAll, // Settings
#[serde(rename = "indexes.create")] const SettingsGet = 1 << 12;
#[deserr(rename = "indexes.create")] const SettingsUpdate = 1 << 13;
IndexesAdd, const SettingsAll = Self::SettingsGet.bits() | Self::SettingsUpdate.bits();
#[serde(rename = "indexes.get")] // Stats
#[deserr(rename = "indexes.get")] const StatsGet = 1 << 14;
IndexesGet, const StatsAll = Self::StatsGet.bits();
#[serde(rename = "indexes.update")] // Metrics
#[deserr(rename = "indexes.update")] const MetricsGet = 1 << 15;
IndexesUpdate, const MetricsAll = Self::MetricsGet.bits();
#[serde(rename = "indexes.delete")] // Dumps
#[deserr(rename = "indexes.delete")] const DumpsCreate = 1 << 16;
IndexesDelete, const DumpsAll = Self::DumpsCreate.bits();
#[serde(rename = "indexes.swap")] // Snapshots
#[deserr(rename = "indexes.swap")] const SnapshotsCreate = 1 << 17;
IndexesSwap, const SnapshotsAll = Self::SnapshotsCreate.bits();
#[serde(rename = "tasks.*")] // Keys without an "all" version
#[deserr(rename = "tasks.*")] const Version = 1 << 18;
TasksAll, const KeysAdd = 1 << 19;
#[serde(rename = "tasks.cancel")] const KeysGet = 1 << 20;
#[deserr(rename = "tasks.cancel")] const KeysUpdate = 1 << 21;
TasksCancel, const KeysDelete = 1 << 22;
#[serde(rename = "tasks.delete")] const ExperimentalFeaturesGet = 1 << 23;
#[deserr(rename = "tasks.delete")] const ExperimentalFeaturesUpdate = 1 << 24;
TasksDelete, // All
#[serde(rename = "tasks.get")] const All = 0xFFFFFFFF >> (32 - 1 - 24);
#[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,
} }
impl Action { impl Action {
pub const fn from_repr(repr: u8) -> Option<Self> { const SERDE_MAP_ARR: [(&'static str, Self); 34] = [
use actions::*; ("search", Self::Search),
match repr { ("documents.add", Self::DocumentsAdd),
ALL => Some(Self::All), ("documents.get", Self::DocumentsGet),
SEARCH => Some(Self::Search), ("documents.delete", Self::DocumentsDelete),
DOCUMENTS_ALL => Some(Self::DocumentsAll), ("documents.*", Self::DocumentsAll),
DOCUMENTS_ADD => Some(Self::DocumentsAdd), ("indexes.create", Self::IndexesAdd),
DOCUMENTS_GET => Some(Self::DocumentsGet), ("indexes.get", Self::IndexesGet),
DOCUMENTS_DELETE => Some(Self::DocumentsDelete), ("indexes.update", Self::IndexesUpdate),
INDEXES_ALL => Some(Self::IndexesAll), ("indexes.delete", Self::IndexesDelete),
INDEXES_CREATE => Some(Self::IndexesAdd), ("indexes.swap", Self::IndexesSwap),
INDEXES_GET => Some(Self::IndexesGet), ("indexes.*", Self::IndexesAll),
INDEXES_UPDATE => Some(Self::IndexesUpdate), ("tasks.cancel", Self::TasksCancel),
INDEXES_DELETE => Some(Self::IndexesDelete), ("tasks.delete", Self::TasksDelete),
INDEXES_SWAP => Some(Self::IndexesSwap), ("tasks.get", Self::TasksGet),
TASKS_ALL => Some(Self::TasksAll), ("tasks.*", Self::TasksAll),
TASKS_CANCEL => Some(Self::TasksCancel), ("settings.get", Self::SettingsGet),
TASKS_DELETE => Some(Self::TasksDelete), ("settings.update", Self::SettingsUpdate),
TASKS_GET => Some(Self::TasksGet), ("settings.*", Self::SettingsAll),
SETTINGS_ALL => Some(Self::SettingsAll), ("stats.get", Self::StatsGet),
SETTINGS_GET => Some(Self::SettingsGet), ("stats.*", Self::StatsAll),
SETTINGS_UPDATE => Some(Self::SettingsUpdate), ("metrics.get", Self::MetricsGet),
STATS_ALL => Some(Self::StatsAll), ("metrics.*", Self::MetricsAll),
STATS_GET => Some(Self::StatsGet), ("dumps.create", Self::DumpsCreate),
METRICS_ALL => Some(Self::MetricsAll), ("dumps.*", Self::DumpsAll),
METRICS_GET => Some(Self::MetricsGet), ("snapshots.create", Self::SnapshotsCreate),
DUMPS_ALL => Some(Self::DumpsAll), ("snapshots.*", Self::SnapshotsAll),
DUMPS_CREATE => Some(Self::DumpsCreate), ("version", Self::Version),
SNAPSHOTS_CREATE => Some(Self::SnapshotsCreate), ("keys.create", Self::KeysAdd),
VERSION => Some(Self::Version), ("keys.get", Self::KeysGet),
KEYS_CREATE => Some(Self::KeysAdd), ("keys.update", Self::KeysUpdate),
KEYS_GET => Some(Self::KeysGet), ("keys.delete", Self::KeysDelete),
KEYS_UPDATE => Some(Self::KeysUpdate), ("experimental.get", Self::ExperimentalFeaturesGet),
KEYS_DELETE => Some(Self::KeysDelete), ("experimental.update", Self::ExperimentalFeaturesUpdate),
EXPERIMENTAL_FEATURES_GET => Some(Self::ExperimentalFeaturesGet), ("*", Self::All),
EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), ];
_otherwise => None,
} fn get_action(v: &str) -> Option<Action> {
Self::SERDE_MAP_ARR
.iter()
.find(|(serde_name, _)| &v == serde_name)
.map(|(_, action)| *action)
} }
pub const fn repr(&self) -> u8 { fn get_action_serde_name(v: &Action) -> &'static str {
*self as u8 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 { pub mod actions {
use super::Action::*; use super::Action as A;
pub(crate) const ALL: u8 = All.repr(); pub const SEARCH: u32 = A::Search.bits();
pub const SEARCH: u8 = Search.repr(); pub const DOCUMENTS_ADD: u32 = A::DocumentsAdd.bits();
pub const DOCUMENTS_ALL: u8 = DocumentsAll.repr(); pub const DOCUMENTS_GET: u32 = A::DocumentsGet.bits();
pub const DOCUMENTS_ADD: u8 = DocumentsAdd.repr(); pub const DOCUMENTS_DELETE: u32 = A::DocumentsDelete.bits();
pub const DOCUMENTS_GET: u8 = DocumentsGet.repr(); pub const DOCUMENTS_ALL: u32 = A::DocumentsAll.bits();
pub const DOCUMENTS_DELETE: u8 = DocumentsDelete.repr(); pub const INDEXES_CREATE: u32 = A::IndexesAdd.bits();
pub const INDEXES_ALL: u8 = IndexesAll.repr(); pub const INDEXES_GET: u32 = A::IndexesGet.bits();
pub const INDEXES_CREATE: u8 = IndexesAdd.repr(); pub const INDEXES_UPDATE: u32 = A::IndexesUpdate.bits();
pub const INDEXES_GET: u8 = IndexesGet.repr(); pub const INDEXES_DELETE: u32 = A::IndexesDelete.bits();
pub const INDEXES_UPDATE: u8 = IndexesUpdate.repr(); pub const INDEXES_SWAP: u32 = A::IndexesSwap.bits();
pub const INDEXES_DELETE: u8 = IndexesDelete.repr(); pub const INDEXES_ALL: u32 = A::IndexesAll.bits();
pub const INDEXES_SWAP: u8 = IndexesSwap.repr(); pub const TASKS_CANCEL: u32 = A::TasksCancel.bits();
pub const TASKS_ALL: u8 = TasksAll.repr(); pub const TASKS_DELETE: u32 = A::TasksDelete.bits();
pub const TASKS_CANCEL: u8 = TasksCancel.repr(); pub const TASKS_GET: u32 = A::TasksGet.bits();
pub const TASKS_DELETE: u8 = TasksDelete.repr(); pub const TASKS_ALL: u32 = A::TasksAll.bits();
pub const TASKS_GET: u8 = TasksGet.repr(); pub const SETTINGS_GET: u32 = A::SettingsGet.bits();
pub const SETTINGS_ALL: u8 = SettingsAll.repr(); pub const SETTINGS_UPDATE: u32 = A::SettingsUpdate.bits();
pub const SETTINGS_GET: u8 = SettingsGet.repr(); pub const SETTINGS_ALL: u32 = A::SettingsAll.bits();
pub const SETTINGS_UPDATE: u8 = SettingsUpdate.repr(); pub const STATS_GET: u32 = A::StatsGet.bits();
pub const STATS_ALL: u8 = StatsAll.repr(); pub const STATS_ALL: u32 = A::StatsAll.bits();
pub const STATS_GET: u8 = StatsGet.repr(); pub const METRICS_GET: u32 = A::MetricsGet.bits();
pub const METRICS_ALL: u8 = MetricsAll.repr(); pub const METRICS_ALL: u32 = A::MetricsAll.bits();
pub const METRICS_GET: u8 = MetricsGet.repr(); pub const DUMPS_CREATE: u32 = A::DumpsCreate.bits();
pub const DUMPS_ALL: u8 = DumpsAll.repr(); pub const DUMPS_ALL: u32 = A::DumpsAll.bits();
pub const DUMPS_CREATE: u8 = DumpsCreate.repr(); pub const SNAPSHOTS_CREATE: u32 = A::SnapshotsCreate.bits();
pub const SNAPSHOTS_CREATE: u8 = SnapshotsCreate.repr(); pub const SNAPSHOTS_ALL: u32 = A::SnapshotsAll.bits();
pub const VERSION: u8 = Version.repr(); pub const VERSION: u32 = A::Version.bits();
pub const KEYS_CREATE: u8 = KeysAdd.repr(); pub const KEYS_CREATE: u32 = A::KeysAdd.bits();
pub const KEYS_GET: u8 = KeysGet.repr(); pub const KEYS_GET: u32 = A::KeysGet.bits();
pub const KEYS_UPDATE: u8 = KeysUpdate.repr(); pub const KEYS_UPDATE: u32 = A::KeysUpdate.bits();
pub const KEYS_DELETE: u8 = KeysDelete.repr(); pub const KEYS_DELETE: u32 = A::KeysDelete.bits();
pub const EXPERIMENTAL_FEATURES_GET: u8 = ExperimentalFeaturesGet.repr(); pub const EXPERIMENTAL_FEATURES_GET: u32 = A::ExperimentalFeaturesGet.bits();
pub const EXPERIMENTAL_FEATURES_UPDATE: u8 = ExperimentalFeaturesUpdate.repr(); pub const EXPERIMENTAL_FEATURES_UPDATE: u32 = A::ExperimentalFeaturesUpdate.bits();
pub const ALL: u32 = A::All.bits();
}
impl<E: DeserializeError> Deserr<E> for Action {
fn deserialize_from_value<V: deserr::IntoValue>(
value: deserr::Value<V>,
location: deserr::ValuePointerRef<'_>,
) -> Result<Self, E> {
match value {
deserr::Value::String(s) => match Self::get_action(&s) {
Some(action) => Ok(action),
None => Err(deserr::take_cf_content(E::error::<std::convert::Infallible>(
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(Self::get_action_serde_name(self))
}
}
impl<'de> Deserialize<'de> for Action {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, s: &str) -> Result<Self::Value, E>
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<Self> {
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<Self> {
// -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<Self> = None;
let mut pre_previous_item: Option<Self> = 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> {
Self::FLAGS.first().map(|v| *v.value())
}
fn last() -> Option<Self> {
Self::FLAGS.last().map(|v| *v.value())
}
} }

View File

@ -171,7 +171,7 @@ pub mod policies {
#[error("Could not decode tenant token, {0}.")] #[error("Could not decode tenant token, {0}.")]
CouldNotDecodeTenantToken(jsonwebtoken::errors::Error), CouldNotDecodeTenantToken(jsonwebtoken::errors::Error),
#[error("Invalid action `{0}`.")] #[error("Invalid action `{0}`.")]
InternalInvalidAction(u8), InternalInvalidAction(u32),
} }
impl From<jsonwebtoken::errors::Error> for AuthError { impl From<jsonwebtoken::errors::Error> for AuthError {
@ -214,14 +214,14 @@ pub mod policies {
Ok(api_key_uid) Ok(api_key_uid)
} }
fn is_keys_action(action: u8) -> bool { fn is_keys_action(action: u32) -> bool {
use actions::*; use actions::*;
matches!(action, KEYS_GET | KEYS_CREATE | KEYS_UPDATE | KEYS_DELETE) matches!(action, KEYS_GET | KEYS_CREATE | KEYS_UPDATE | KEYS_DELETE)
} }
pub struct ActionPolicy<const A: u8>; pub struct ActionPolicy<const A: u32>;
impl<const A: u8> Policy for ActionPolicy<A> { impl<const A: u32> Policy for ActionPolicy<A> {
/// Attempts to grant authentication from a bearer token (that can be a tenant token or an API key), the requested Action, /// 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. /// and a list of requested indexes.
/// ///
@ -255,7 +255,7 @@ pub mod policies {
}; };
// check that the indexes are allowed // 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 let auth_filter = auth
.get_key_filters(key_uuid, search_rules) .get_key_filters(key_uuid, search_rules)
.map_err(|_e| AuthError::InvalidApiKey)?; .map_err(|_e| AuthError::InvalidApiKey)?;
@ -294,7 +294,7 @@ pub mod policies {
} }
} }
impl<const A: u8> ActionPolicy<A> { impl<const A: u32> ActionPolicy<A> {
fn authenticate_tenant_token( fn authenticate_tenant_token(
auth: &AuthController, auth: &AuthController,
token: &str, token: &str,

View File

@ -421,7 +421,7 @@ async fn error_add_api_key_invalid_parameters_actions() {
meili_snap::snapshot!(code, @"400 Bad Request"); meili_snap::snapshot!(code, @"400 Bad Request");
meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###" 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", "code": "invalid_api_key_actions",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"

View File

@ -53,14 +53,14 @@ pub static AUTHORIZATIONS: Lazy<HashMap<(&'static str, &'static str), HashSet<&'
("PUT", "/indexes/products/settings/sortable-attributes") => hashset!{"settings.update", "settings.*", "*"}, ("PUT", "/indexes/products/settings/sortable-attributes") => hashset!{"settings.update", "settings.*", "*"},
("PUT", "/indexes/products/settings/stop-words") => hashset!{"settings.update", "settings.*", "*"}, ("PUT", "/indexes/products/settings/stop-words") => hashset!{"settings.update", "settings.*", "*"},
("PUT", "/indexes/products/settings/synonyms") => hashset!{"settings.update", "settings.*", "*"}, ("PUT", "/indexes/products/settings/synonyms") => hashset!{"settings.update", "settings.*", "*"},
("GET", "/indexes/products/stats") => hashset!{"stats.get", "stats.*", "*"}, ("GET", "/indexes/products/stats") => hashset!{"stats.get", "*"},
("GET", "/stats") => hashset!{"stats.get", "stats.*", "*"}, ("GET", "/stats") => hashset!{"stats.get", "*"},
("POST", "/dumps") => hashset!{"dumps.create", "dumps.*", "*"}, ("POST", "/dumps") => hashset!{"dumps.create", "*"},
("POST", "/snapshots") => hashset!{"snapshots.create", "snapshots.*", "*"}, ("POST", "/snapshots") => hashset!{"snapshots.create", "*"},
("GET", "/version") => hashset!{"version", "*"}, ("GET", "/version") => hashset!{"version", "*"},
("GET", "/metrics") => hashset!{"metrics.get", "metrics.*", "*"}, ("GET", "/metrics") => hashset!{"metrics.get", "*"},
("POST", "/logs/stream") => hashset!{"metrics.get", "metrics.*", "*"}, ("POST", "/logs/stream") => hashset!{"metrics.get", "*"},
("DELETE", "/logs/stream") => hashset!{"metrics.get", "metrics.*", "*"}, ("DELETE", "/logs/stream") => hashset!{"metrics.get", "*"},
("PATCH", "/keys/mykey/") => hashset!{"keys.update", "*"}, ("PATCH", "/keys/mykey/") => hashset!{"keys.update", "*"},
("GET", "/keys/mykey/") => hashset!{"keys.get", "*"}, ("GET", "/keys/mykey/") => hashset!{"keys.get", "*"},
("DELETE", "/keys/mykey/") => hashset!{"keys.delete", "*"}, ("DELETE", "/keys/mykey/") => hashset!{"keys.delete", "*"},

View File

@ -93,7 +93,7 @@ async fn create_api_key_bad_actions() {
snapshot!(code, @"400 Bad Request"); snapshot!(code, @"400 Bad Request");
snapshot!(json_string!(response), @r###" 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", "code": "invalid_api_key_actions",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions"