2021-11-09 01:31:27 +08:00
|
|
|
mod action;
|
2021-12-06 22:45:41 +08:00
|
|
|
mod dump;
|
2021-11-09 01:31:27 +08:00
|
|
|
pub mod error;
|
|
|
|
mod key;
|
|
|
|
mod store;
|
|
|
|
|
2022-01-12 22:35:33 +08:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2022-06-06 18:45:52 +08:00
|
|
|
use std::ops::Deref;
|
2021-11-09 01:31:27 +08:00
|
|
|
use std::path::Path;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
2022-01-12 22:35:33 +08:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-11-09 01:31:27 +08:00
|
|
|
use serde_json::Value;
|
2022-02-14 22:32:41 +08:00
|
|
|
use time::OffsetDateTime;
|
2022-06-02 00:06:20 +08:00
|
|
|
use uuid::Uuid;
|
2021-11-09 01:31:27 +08:00
|
|
|
|
|
|
|
pub use action::{actions, Action};
|
|
|
|
use error::{AuthControllerError, Result};
|
|
|
|
pub use key::Key;
|
2022-06-06 18:45:52 +08:00
|
|
|
use meilisearch_types::star_or::StarOr;
|
2022-07-05 20:13:20 +08:00
|
|
|
use store::generate_key_as_hexa;
|
2022-02-23 01:16:02 +08:00
|
|
|
pub use store::open_auth_store_env;
|
2021-11-09 01:31:27 +08:00
|
|
|
use store::HeedAuthStore;
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct AuthController {
|
|
|
|
store: Arc<HeedAuthStore>,
|
|
|
|
master_key: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AuthController {
|
|
|
|
pub fn new(db_path: impl AsRef<Path>, master_key: &Option<String>) -> Result<Self> {
|
|
|
|
let store = HeedAuthStore::new(db_path)?;
|
|
|
|
|
|
|
|
if store.is_empty()? {
|
|
|
|
generate_default_keys(&store)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
store: Arc::new(store),
|
|
|
|
master_key: master_key.clone(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-03-03 01:22:34 +08:00
|
|
|
pub fn create_key(&self, value: Value) -> Result<Key> {
|
2021-11-09 01:31:27 +08:00
|
|
|
let key = Key::create_from_value(value)?;
|
2022-05-25 16:32:47 +08:00
|
|
|
match self.store.get_api_key(key.uid)? {
|
|
|
|
Some(_) => Err(AuthControllerError::ApiKeyAlreadyExists(
|
|
|
|
key.uid.to_string(),
|
|
|
|
)),
|
|
|
|
None => self.store.put_api_key(key),
|
|
|
|
}
|
2021-11-09 01:31:27 +08:00
|
|
|
}
|
|
|
|
|
2022-05-25 16:32:47 +08:00
|
|
|
pub fn update_key(&self, uid: Uuid, value: Value) -> Result<Key> {
|
|
|
|
let mut key = self.get_key(uid)?;
|
2021-11-09 01:31:27 +08:00
|
|
|
key.update_from_value(value)?;
|
|
|
|
self.store.put_api_key(key)
|
|
|
|
}
|
|
|
|
|
2022-05-25 16:32:47 +08:00
|
|
|
pub fn get_key(&self, uid: Uuid) -> Result<Key> {
|
2021-11-09 01:31:27 +08:00
|
|
|
self.store
|
2022-05-25 16:32:47 +08:00
|
|
|
.get_api_key(uid)?
|
|
|
|
.ok_or_else(|| AuthControllerError::ApiKeyNotFound(uid.to_string()))
|
|
|
|
}
|
|
|
|
|
2022-06-02 00:06:20 +08:00
|
|
|
pub fn get_optional_uid_from_encoded_key(&self, encoded_key: &[u8]) -> Result<Option<Uuid>> {
|
2022-05-25 16:32:47 +08:00
|
|
|
match &self.master_key {
|
2022-06-02 00:06:20 +08:00
|
|
|
Some(master_key) => self
|
|
|
|
.store
|
|
|
|
.get_uid_from_encoded_key(encoded_key, master_key.as_bytes()),
|
2022-05-25 16:32:47 +08:00
|
|
|
None => Ok(None),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-02 00:06:20 +08:00
|
|
|
pub fn get_uid_from_encoded_key(&self, encoded_key: &str) -> Result<Uuid> {
|
|
|
|
self.get_optional_uid_from_encoded_key(encoded_key.as_bytes())?
|
|
|
|
.ok_or_else(|| AuthControllerError::ApiKeyNotFound(encoded_key.to_string()))
|
2021-11-09 01:31:27 +08:00
|
|
|
}
|
|
|
|
|
2022-01-12 22:35:33 +08:00
|
|
|
pub fn get_key_filters(
|
|
|
|
&self,
|
2022-05-25 16:32:47 +08:00
|
|
|
uid: Uuid,
|
2022-01-12 22:35:33 +08:00
|
|
|
search_rules: Option<SearchRules>,
|
|
|
|
) -> Result<AuthFilter> {
|
2021-11-09 01:31:27 +08:00
|
|
|
let mut filters = AuthFilter::default();
|
2022-05-25 16:32:47 +08:00
|
|
|
let key = self
|
|
|
|
.store
|
|
|
|
.get_api_key(uid)?
|
|
|
|
.ok_or_else(|| AuthControllerError::ApiKeyNotFound(uid.to_string()))?;
|
|
|
|
|
2022-06-06 18:45:52 +08:00
|
|
|
if !key.indexes.iter().any(|i| i == &StarOr::Star) {
|
2022-05-25 16:32:47 +08:00
|
|
|
filters.search_rules = match search_rules {
|
|
|
|
// Intersect search_rules with parent key authorized indexes.
|
|
|
|
Some(search_rules) => SearchRules::Map(
|
|
|
|
key.indexes
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|index| {
|
2022-06-06 18:45:52 +08:00
|
|
|
search_rules.get_index_search_rules(index.deref()).map(
|
|
|
|
|index_search_rules| {
|
|
|
|
(String::from(index), Some(index_search_rules))
|
|
|
|
},
|
|
|
|
)
|
2022-05-25 16:32:47 +08:00
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
),
|
2022-06-06 18:45:52 +08:00
|
|
|
None => SearchRules::Set(key.indexes.into_iter().map(String::from).collect()),
|
2022-05-25 16:32:47 +08:00
|
|
|
};
|
|
|
|
} else if let Some(search_rules) = search_rules {
|
|
|
|
filters.search_rules = search_rules;
|
2021-11-09 01:31:27 +08:00
|
|
|
}
|
|
|
|
|
2022-05-25 16:32:47 +08:00
|
|
|
filters.allow_index_creation = key
|
|
|
|
.actions
|
|
|
|
.iter()
|
|
|
|
.any(|&action| action == Action::IndexesAdd || action == Action::All);
|
|
|
|
|
2021-11-09 01:31:27 +08:00
|
|
|
Ok(filters)
|
|
|
|
}
|
|
|
|
|
2022-03-03 01:22:34 +08:00
|
|
|
pub fn list_keys(&self) -> Result<Vec<Key>> {
|
2021-11-09 01:31:27 +08:00
|
|
|
self.store.list_api_keys()
|
|
|
|
}
|
|
|
|
|
2022-05-25 16:32:47 +08:00
|
|
|
pub fn delete_key(&self, uid: Uuid) -> Result<()> {
|
|
|
|
if self.store.delete_api_key(uid)? {
|
2021-11-09 01:31:27 +08:00
|
|
|
Ok(())
|
|
|
|
} else {
|
2022-05-25 16:32:47 +08:00
|
|
|
Err(AuthControllerError::ApiKeyNotFound(uid.to_string()))
|
2021-11-09 01:31:27 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_master_key(&self) -> Option<&String> {
|
|
|
|
self.master_key.as_ref()
|
|
|
|
}
|
|
|
|
|
2022-01-12 22:35:33 +08:00
|
|
|
/// Generate a valid key from a key id using the current master key.
|
|
|
|
/// Returns None if no master key has been set.
|
2022-05-25 16:32:47 +08:00
|
|
|
pub fn generate_key(&self, uid: Uuid) -> Option<String> {
|
2022-01-12 22:35:33 +08:00
|
|
|
self.master_key
|
|
|
|
.as_ref()
|
2022-07-05 20:13:20 +08:00
|
|
|
.map(|master_key| generate_key_as_hexa(uid, master_key.as_bytes()))
|
2022-01-12 22:35:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Check if the provided key is authorized to make a specific action
|
|
|
|
/// without checking if the key is valid.
|
|
|
|
pub fn is_key_authorized(
|
|
|
|
&self,
|
2022-05-25 16:32:47 +08:00
|
|
|
uid: Uuid,
|
2022-01-12 22:35:33 +08:00
|
|
|
action: Action,
|
|
|
|
index: Option<&str>,
|
|
|
|
) -> Result<bool> {
|
|
|
|
match self
|
|
|
|
.store
|
|
|
|
// check if the key has access to all indexes.
|
2022-05-25 16:32:47 +08:00
|
|
|
.get_expiration_date(uid, action, None)?
|
2022-01-12 22:35:33 +08:00
|
|
|
.or(match index {
|
|
|
|
// else check if the key has access to the requested index.
|
|
|
|
Some(index) => {
|
|
|
|
self.store
|
2022-05-25 16:32:47 +08:00
|
|
|
.get_expiration_date(uid, action, Some(index.as_bytes()))?
|
2021-11-09 01:31:27 +08:00
|
|
|
}
|
2022-01-12 22:35:33 +08:00
|
|
|
// or to any index if no index has been requested.
|
2022-05-25 16:32:47 +08:00
|
|
|
None => self.store.prefix_first_expiration_date(uid, action)?,
|
2022-01-12 22:35:33 +08:00
|
|
|
}) {
|
|
|
|
// check expiration date.
|
2022-02-14 22:32:41 +08:00
|
|
|
Some(Some(exp)) => Ok(OffsetDateTime::now_utc() < exp),
|
2022-01-12 22:35:33 +08:00
|
|
|
// no expiration date.
|
|
|
|
Some(None) => Ok(true),
|
|
|
|
// action or index forbidden.
|
|
|
|
None => Ok(false),
|
|
|
|
}
|
|
|
|
}
|
2021-11-09 01:31:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct AuthFilter {
|
2022-01-12 22:35:33 +08:00
|
|
|
pub search_rules: SearchRules,
|
2021-12-15 21:52:33 +08:00
|
|
|
pub allow_index_creation: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for AuthFilter {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2022-01-12 22:35:33 +08:00
|
|
|
search_rules: SearchRules::default(),
|
2021-12-15 21:52:33 +08:00
|
|
|
allow_index_creation: true,
|
|
|
|
}
|
|
|
|
}
|
2021-11-09 01:31:27 +08:00
|
|
|
}
|
|
|
|
|
2022-01-12 22:35:33 +08:00
|
|
|
/// Transparent wrapper around a list of allowed indexes with the search rules to apply for each.
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
pub enum SearchRules {
|
|
|
|
Set(HashSet<String>),
|
|
|
|
Map(HashMap<String, Option<IndexSearchRules>>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for SearchRules {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Set(Some("*".to_string()).into_iter().collect())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SearchRules {
|
|
|
|
pub fn is_index_authorized(&self, index: &str) -> bool {
|
|
|
|
match self {
|
|
|
|
Self::Set(set) => set.contains("*") || set.contains(index),
|
|
|
|
Self::Map(map) => map.contains_key("*") || map.contains_key(index),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_index_search_rules(&self, index: &str) -> Option<IndexSearchRules> {
|
|
|
|
match self {
|
|
|
|
Self::Set(set) => {
|
|
|
|
if set.contains("*") || set.contains(index) {
|
|
|
|
Some(IndexSearchRules::default())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Self::Map(map) => map
|
|
|
|
.get(index)
|
|
|
|
.or_else(|| map.get("*"))
|
|
|
|
.map(|isr| isr.clone().unwrap_or_default()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IntoIterator for SearchRules {
|
|
|
|
type Item = (String, IndexSearchRules);
|
|
|
|
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
|
|
|
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
|
|
match self {
|
|
|
|
Self::Set(array) => {
|
|
|
|
Box::new(array.into_iter().map(|i| (i, IndexSearchRules::default())))
|
|
|
|
}
|
|
|
|
Self::Map(map) => {
|
|
|
|
Box::new(map.into_iter().map(|(i, isr)| (i, isr.unwrap_or_default())))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Contains the rules to apply on the top of the search query for a specific index.
|
|
|
|
///
|
|
|
|
/// filter: search filter to apply in addition to query filters.
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
|
|
|
pub struct IndexSearchRules {
|
|
|
|
pub filter: Option<serde_json::Value>,
|
|
|
|
}
|
|
|
|
|
2021-11-09 01:31:27 +08:00
|
|
|
fn generate_default_keys(store: &HeedAuthStore) -> Result<()> {
|
|
|
|
store.put_api_key(Key::default_admin())?;
|
|
|
|
store.put_api_key(Key::default_search())?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|