Skip to content

Commit

Permalink
added check
Browse files Browse the repository at this point in the history
  • Loading branch information
fulmicoton committed Nov 1, 2024
1 parent 5a924f6 commit 002d98b
Show file tree
Hide file tree
Showing 46 changed files with 1,177 additions and 538 deletions.
1 change: 1 addition & 0 deletions quickwit/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion quickwit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ members = [
"quickwit-serve",
"quickwit-storage",
"quickwit-telemetry",
"quickwit-telemetry",
]

# The following list excludes `quickwit-metastore-utils` and `quickwit-lambda`
Expand Down
21 changes: 21 additions & 0 deletions quickwit/quickwit-auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "quickwit-auth"
version.workspace = true
edition.workspace = true
homepage.workspace = true
documentation.workspace = true
repository.workspace = true
authors.workspace = true
license.workspace = true

[dependencies]
biscuit-auth = { workspace = true, optional=true }
http = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
tonic = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

[features]
enterprise = ["biscuit-auth"]
87 changes: 87 additions & 0 deletions quickwit/quickwit-auth/src/community.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (C) 2024 Quickwit, Inc.
//
// Quickwit is offered under the AGPL v3.0 and as commercial software.
// For commercial licensing, contact us at [email protected].
//
// AGPL:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use std::future::Future;

use crate::AuthorizationError;

pub type AuthorizationToken = ();

pub trait Authorization {
fn attenuate(
&self,
_auth_token: AuthorizationToken,
) -> Result<AuthorizationToken, AuthorizationError> {
Ok(())
}
}

impl<T> Authorization for T {}

pub trait StreamAuthorization {
fn attenuate(
_auth_token: AuthorizationToken,
) -> std::result::Result<AuthorizationToken, AuthorizationError> {
Ok(())
}
}

impl<T> StreamAuthorization for T {}

pub fn get_auth_token(
_req_metadata: &tonic::metadata::MetadataMap,
) -> Result<AuthorizationToken, AuthorizationError> {
Ok(())
}

pub fn set_auth_token(
_auth_token: &AuthorizationToken,
_req_metadata: &mut tonic::metadata::MetadataMap,
) {
}

pub fn authorize<R: Authorization>(
_req: &R,
_auth_token: &AuthorizationToken,
) -> Result<(), AuthorizationError> {
Ok(())
}

pub fn build_tonic_stream_request_with_auth_token<R>(
req: R,
) -> Result<tonic::Request<R>, AuthorizationError> {
Ok(tonic::Request::new(req))
}

pub fn build_tonic_request_with_auth_token<R: Authorization>(
req: R,
) -> Result<tonic::Request<R>, AuthorizationError> {
Ok(tonic::Request::new(req))
}

pub fn authorize_stream<R: StreamAuthorization>(
_auth_token: &AuthorizationToken,
) -> Result<(), AuthorizationError> {
Ok(())
}

pub fn execute_with_authorization<F, O>(_: AuthorizationToken, f: F) -> impl Future<Output = O>
where F: Future<Output = O> {
f
}
259 changes: 259 additions & 0 deletions quickwit/quickwit-auth/src/enterprise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
// The Quickwit Enterprise Edition (EE) license
// Copyright (c) 2024-present Quickwit Inc.
//
// With regard to the Quickwit Software:
//
// This software and associated documentation files (the "Software") may only be
// used in production, if you (and any entity that you represent) hold a valid
// Quickwit Enterprise license corresponding to your usage.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// For all third party components incorporated into the Quickwit Software, those
// components are licensed under the original license provided by the owner of the
// applicable component.

use std::future::Future;
use std::str::FromStr;
use std::sync::{Arc, OnceLock};

use biscuit_auth::macros::authorizer;
use biscuit_auth::{Authorizer, Biscuit, RootKeyProvider};

use crate::AuthorizationError;

pub struct AuthorizationToken(Biscuit);

impl AuthorizationToken {
pub fn into_biscuit(self) -> Biscuit {
self.0
}
}

impl From<Biscuit> for AuthorizationToken {
fn from(biscuit: Biscuit) -> Self {
AuthorizationToken(biscuit)
}
}

impl std::fmt::Display for AuthorizationToken {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}

impl std::fmt::Debug for AuthorizationToken {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "AuthorizationToken({})", &self.0)
}
}

static ROOT_KEY_PROVIDER: OnceLock<Arc<dyn RootKeyProvider + Sync + Send>> = OnceLock::new();

pub fn set_root_key_provider(key_provider: Arc<dyn RootKeyProvider + Sync + Send>) {
if ROOT_KEY_PROVIDER.set(key_provider).is_err() {
tracing::error!("root key provider was already initialized");
}
}

fn get_root_key_provider() -> Arc<dyn RootKeyProvider> {
ROOT_KEY_PROVIDER
.get()
.expect("root key provider should have been initialized beforehand")
.clone()
}

impl FromStr for AuthorizationToken {
type Err = AuthorizationError;

fn from_str(token_base64: &str) -> Result<Self, AuthorizationError> {
let root_key_provider = get_root_key_provider();
let biscuit = Biscuit::from_base64(token_base64, root_key_provider)?;
Ok(AuthorizationToken(biscuit))
}
}

tokio::task_local! {
pub static AUTHORIZATION_TOKEN: AuthorizationToken;
}

const AUTHORIZATION_VALUE_PREFIX: &str = "Bearer ";

fn default_operation_authorizer<T: ?Sized>(
auth_token: &AuthorizationToken,
) -> Result<Authorizer, AuthorizationError> {
let request_type = std::any::type_name::<T>();
let operation: &str = request_type.strip_suffix("Request").unwrap();
let mut authorizer: Authorizer = authorizer!(
r#"
operation({operation});
// We generate the actual user role, by doing an union of the rights granted via roles.
user_right($operation) <- role($role), right($role, $operation);
user_right($operation, $resource) <- role($role), right($role, $operation, $resource);
user_right($operation) <- role("root"), operation($operation);
user_right($operation, $resource) <- role("root"), operation($operation), resource($resource);
// Finally we check that we have access to index1 and index2.
check all operation($operation), right($operation);
allow if true;
"#
);
authorizer.set_time();
authorizer.add_token(&auth_token.0)?;
Ok(authorizer)
}

pub trait Authorization {
fn attenuate(
&self,
auth_token: AuthorizationToken,
) -> Result<AuthorizationToken, AuthorizationError>;
fn authorizer(
&self,
auth_token: &AuthorizationToken,
) -> Result<Authorizer, AuthorizationError> {
default_operation_authorizer::<Self>(auth_token)
}
}

pub trait StreamAuthorization {
fn attenuate(
auth_token: AuthorizationToken,
) -> std::result::Result<AuthorizationToken, AuthorizationError> {
Ok(auth_token)
}
fn authorizer(
auth_token: &AuthorizationToken,
) -> std::result::Result<Authorizer, AuthorizationError> {
default_operation_authorizer::<Self>(&auth_token)
}
}

impl From<biscuit_auth::error::Token> for AuthorizationError {
fn from(_token_error: biscuit_auth::error::Token) -> AuthorizationError {
AuthorizationError::InvalidToken
}
}

pub fn get_auth_token(
req_metadata: &tonic::metadata::MetadataMap,
) -> Result<AuthorizationToken, AuthorizationError> {
let authorization_header_value: &str = req_metadata
.get(http::header::AUTHORIZATION.as_str())
.ok_or(AuthorizationError::AuthorizationTokenMissing)?
.to_str()
.map_err(|_| AuthorizationError::InvalidToken)?;
let authorization_token_str: &str = authorization_header_value
.strip_prefix(AUTHORIZATION_VALUE_PREFIX)
.ok_or(AuthorizationError::InvalidToken)?;
let biscuit: Biscuit = Biscuit::from_base64(authorization_token_str, get_root_key_provider())?;
Ok(AuthorizationToken(biscuit))
}

pub fn set_auth_token(
auth_token: &AuthorizationToken,
req_metadata: &mut tonic::metadata::MetadataMap,
) {
let authorization_header_value = format!("{AUTHORIZATION_VALUE_PREFIX}{auth_token}");
req_metadata.insert(
http::header::AUTHORIZATION.as_str(),
authorization_header_value.parse().unwrap(),
);
}

pub fn authorize<R: Authorization>(
req: &R,
auth_token: &AuthorizationToken,
) -> Result<(), AuthorizationError> {
let mut authorizer = req.authorizer(auth_token)?;
authorizer.add_token(&auth_token.0)?;
authorizer.authorize()?;
Ok(())
}

pub fn build_tonic_stream_request_with_auth_token<R>(
req: R,
) -> Result<tonic::Request<R>, AuthorizationError> {
AUTHORIZATION_TOKEN
.try_with(|token| {
let mut request = tonic::Request::new(req);
set_auth_token(token, request.metadata_mut());
Ok(request)
})
.unwrap_or(Err(AuthorizationError::AuthorizationTokenMissing))
}

pub fn build_tonic_request_with_auth_token<R: Authorization>(
req: R,
) -> Result<tonic::Request<R>, AuthorizationError> {
AUTHORIZATION_TOKEN
.try_with(|token| {
let mut request = tonic::Request::new(req);
set_auth_token(token, request.metadata_mut());
Ok(request)
})
.unwrap_or(Err(AuthorizationError::AuthorizationTokenMissing))
}

pub fn authorize_stream<R: StreamAuthorization>(
auth_token: &AuthorizationToken,
) -> Result<(), AuthorizationError> {
let mut authorizer = R::authorizer(auth_token)?;
authorizer.add_token(&auth_token.0)?;
authorizer.authorize()?;
Ok(())
}

pub fn execute_with_authorization<F, O>(
token: AuthorizationToken,
f: F,
) -> impl Future<Output = O>
where
F: Future<Output = O>,
{
AUTHORIZATION_TOKEN.scope(token, f)
}

#[cfg(test)]
mod tests {
use super::*;

// #[test]
// fn test_auth_token() {
// let mut req_metadata = tonic::metadata::MetadataMap::new();
// let token =
// let auth_token = "test_token".to_string();
// set_auth_token(&auth_token, &mut req_metadata);
// let auth_token_retrieved = get_auth_token(&req_metadata).unwrap();
// assert_eq!(auth_token_retrieved, auth_token);
// }

#[test]
fn test_auth_token_missing() {
let req_metadata = tonic::metadata::MetadataMap::new();
let missing_error = get_auth_token(&req_metadata).unwrap_err();
assert!(matches!(
missing_error,
AuthorizationError::AuthorizationTokenMissing
));
}

#[test]
fn test_auth_token_invalid() {
let mut req_metadata = tonic::metadata::MetadataMap::new();
req_metadata.insert(
http::header::AUTHORIZATION.as_str(),
"some_token".parse().unwrap(),
);
let missing_error = get_auth_token(&req_metadata).unwrap_err();
assert!(matches!(missing_error, AuthorizationError::InvalidToken));
}
}
Loading

0 comments on commit 002d98b

Please sign in to comment.