mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-26 14:54:27 +01:00
Authentication: Refactor authentication check to work for tenant token even without an index in URL
Callers need to manually check `is_index_authorized` when using the route without an index in URL
This commit is contained in:
parent
10d4a1a9af
commit
4b65851793
@ -136,6 +136,13 @@ pub mod policies {
|
|||||||
|
|
||||||
use crate::extractors::authentication::Policy;
|
use crate::extractors::authentication::Policy;
|
||||||
|
|
||||||
|
enum TenantTokenOutcome {
|
||||||
|
NotATenantToken,
|
||||||
|
Invalid,
|
||||||
|
Expired,
|
||||||
|
Valid(Uuid, SearchRules),
|
||||||
|
}
|
||||||
|
|
||||||
fn tenant_token_validation() -> Validation {
|
fn tenant_token_validation() -> Validation {
|
||||||
let mut validation = Validation::default();
|
let mut validation = Validation::default();
|
||||||
validation.validate_exp = false;
|
validation.validate_exp = false;
|
||||||
@ -164,29 +171,42 @@ pub mod policies {
|
|||||||
pub struct ActionPolicy<const A: u8>;
|
pub struct ActionPolicy<const A: u8>;
|
||||||
|
|
||||||
impl<const A: u8> Policy for ActionPolicy<A> {
|
impl<const A: u8> 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,
|
||||||
|
/// and a list of requested indexes.
|
||||||
|
///
|
||||||
|
/// If the bearer token is not allowed for the specified indexes and action, returns `None`.
|
||||||
|
/// Otherwise, returns an object containing the generated permissions: the search filters to add to a search, and the list of allowed indexes
|
||||||
|
/// (that may contain more indexes than requested).
|
||||||
fn authenticate(
|
fn authenticate(
|
||||||
auth: AuthController,
|
auth: AuthController,
|
||||||
token: &str,
|
token: &str,
|
||||||
index: Option<&str>,
|
index: Option<&str>,
|
||||||
) -> Option<AuthFilter> {
|
) -> Option<AuthFilter> {
|
||||||
// authenticate if token is the master key.
|
// authenticate if token is the master key.
|
||||||
// master key can only have access to keys routes.
|
// Without a master key, all routes are accessible except the key-related routes.
|
||||||
// if master key is None only keys routes are inaccessible.
|
|
||||||
if auth.get_master_key().map_or_else(|| !is_keys_action(A), |mk| mk == token) {
|
if auth.get_master_key().map_or_else(|| !is_keys_action(A), |mk| mk == token) {
|
||||||
return Some(AuthFilter::default());
|
return Some(AuthFilter::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tenant token
|
let (key_uuid, search_rules) =
|
||||||
if let Some(filters) = ActionPolicy::<A>::authenticate_tenant_token(&auth, token, index)
|
match ActionPolicy::<A>::authenticate_tenant_token(&auth, token) {
|
||||||
{
|
TenantTokenOutcome::Valid(key_uuid, search_rules) => {
|
||||||
return Some(filters);
|
(key_uuid, Some(search_rules))
|
||||||
} else if let Some(action) = Action::from_repr(A) {
|
|
||||||
// API key
|
|
||||||
if let Ok(Some(uid)) = auth.get_optional_uid_from_encoded_key(token.as_bytes()) {
|
|
||||||
if let Ok(true) = auth.is_key_authorized(uid, action, index) {
|
|
||||||
return auth.get_key_filters(uid, None).ok();
|
|
||||||
}
|
}
|
||||||
}
|
TenantTokenOutcome::Expired => return None,
|
||||||
|
TenantTokenOutcome::Invalid => return None,
|
||||||
|
TenantTokenOutcome::NotATenantToken => {
|
||||||
|
(auth.get_optional_uid_from_encoded_key(token.as_bytes()).ok()??, None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// check that the indexes are allowed
|
||||||
|
let action = Action::from_repr(A)?;
|
||||||
|
let auth_filter = auth.get_key_filters(key_uuid, search_rules).ok()?;
|
||||||
|
if auth.is_key_authorized(key_uuid, action, index).unwrap_or(false)
|
||||||
|
&& index.map(|index| auth_filter.is_index_authorized(index)).unwrap_or(true)
|
||||||
|
{
|
||||||
|
return Some(auth_filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -194,50 +214,43 @@ pub mod policies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const A: u8> ActionPolicy<A> {
|
impl<const A: u8> ActionPolicy<A> {
|
||||||
fn authenticate_tenant_token(
|
fn authenticate_tenant_token(auth: &AuthController, token: &str) -> TenantTokenOutcome {
|
||||||
auth: &AuthController,
|
|
||||||
token: &str,
|
|
||||||
index: Option<&str>,
|
|
||||||
) -> Option<AuthFilter> {
|
|
||||||
// A tenant token only has access to the search route which always defines an index.
|
|
||||||
let index = index?;
|
|
||||||
|
|
||||||
// Only search action can be accessed by a tenant token.
|
// Only search action can be accessed by a tenant token.
|
||||||
if A != actions::SEARCH {
|
if A != actions::SEARCH {
|
||||||
return None;
|
return TenantTokenOutcome::NotATenantToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
let uid = extract_key_id(token)?;
|
let uid = if let Some(uid) = extract_key_id(token) {
|
||||||
// check if parent key is authorized to do the action.
|
uid
|
||||||
if auth.is_key_authorized(uid, Action::Search, Some(index)).ok()? {
|
} else {
|
||||||
// Check if tenant token is valid.
|
return TenantTokenOutcome::NotATenantToken;
|
||||||
let key = auth.generate_key(uid)?;
|
};
|
||||||
let data = decode::<Claims>(
|
|
||||||
token,
|
|
||||||
&DecodingKey::from_secret(key.as_bytes()),
|
|
||||||
&tenant_token_validation(),
|
|
||||||
)
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
// Check index access if an index restriction is provided.
|
// Check if tenant token is valid.
|
||||||
if !data.claims.search_rules.is_index_authorized(index) {
|
let key = if let Some(key) = auth.generate_key(uid) {
|
||||||
return None;
|
key
|
||||||
|
} else {
|
||||||
|
return TenantTokenOutcome::Invalid;
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = if let Ok(data) = decode::<Claims>(
|
||||||
|
token,
|
||||||
|
&DecodingKey::from_secret(key.as_bytes()),
|
||||||
|
&tenant_token_validation(),
|
||||||
|
) {
|
||||||
|
data
|
||||||
|
} else {
|
||||||
|
return TenantTokenOutcome::Invalid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if token is expired.
|
||||||
|
if let Some(exp) = data.claims.exp {
|
||||||
|
if OffsetDateTime::now_utc().unix_timestamp() > exp {
|
||||||
|
return TenantTokenOutcome::Expired;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if token is expired.
|
|
||||||
if let Some(exp) = data.claims.exp {
|
|
||||||
if OffsetDateTime::now_utc().unix_timestamp() > exp {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return match auth.get_key_filters(uid, Some(data.claims.search_rules)) {
|
|
||||||
Ok(auth) if auth.search_rules.is_index_authorized(index) => Some(auth),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
TenantTokenOutcome::Valid(uid, data.claims.search_rules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user