Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions server/src/handlers/http/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ use actix_web::{
use actix_web_httpauth::extractors::basic::BasicAuth;
use futures_util::future::LocalBoxFuture;

use crate::{
option::CONFIG,
rbac::{role::Action, Users},
};
use crate::{option::CONFIG, rbac::role::Action, rbac::Users};

pub struct Auth {
pub action: Action,
Expand Down Expand Up @@ -77,7 +74,7 @@ where
forward_ready!(service);

fn call(&self, mut req: ServiceRequest) -> Self::Future {
// Extract username and password using basic auth extractor.
// Extract username and password from the request using basic auth extractor.
let creds = req.extract::<BasicAuth>().into_inner();
let creds = creds.map_err(Into::into).map(|creds| {
let username = creds.user_id().trim().to_owned();
Expand Down
4 changes: 2 additions & 2 deletions server/src/handlers/http/rbac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub async fn delete_user(username: web::Path<String>) -> Result<impl Responder,
// Reset password for given username
// returns new password generated for this user
pub async fn reset_password(username: String) -> Result<String, RBACError> {
// get new password for this user
// generate new password for this user
let PassCode { password, hash } = User::gen_new_password();
// update parseable.json first
let mut metadata = get_metadata().await?;
Expand Down Expand Up @@ -145,7 +145,7 @@ pub async fn put_role(
put_metadata(&metadata).await?;
// update in mem table
Users.put_role(&username, role);
Ok(format!("Roles updated successfully for {}", username))
Ok(format!("Roles updated successfully for {username}"))
}

async fn get_metadata() -> Result<crate::storage::StorageMetadata, ObjectStorageError> {
Expand Down
2 changes: 1 addition & 1 deletion server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async fn main() -> anyhow::Result<()> {
migration::run_metadata_migration(&CONFIG).await?;
let metadata = storage::resolve_parseable_metadata().await?;
banner::print(&CONFIG, &metadata).await;
rbac::set_user_map(metadata.users.clone());
rbac::map::init_auth_maps(metadata.users.clone());
metadata.set_global();
let prometheus = metrics::build_metrics_handler();
CONFIG.storage().register_store_metrics(&prometheus);
Expand Down
79 changes: 10 additions & 69 deletions server/src/rbac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,16 @@
*
*/

use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};

use once_cell::sync::OnceCell;

use crate::option::CONFIG;

use self::{
auth::AuthMap,
role::{model::DefaultPrivilege, Action},
user::{get_admin_user, User, UserMap},
};

pub mod auth;
pub mod map;
pub mod role;
pub mod user;

pub static USER_MAP: OnceCell<RwLock<UserMap>> = OnceCell::new();
pub static AUTH_MAP: OnceCell<RwLock<AuthMap>> = OnceCell::new();

fn user_map() -> RwLockReadGuard<'static, UserMap> {
USER_MAP
.get()
.expect("map is set")
.read()
.expect("not poisoned")
}

fn mut_user_map() -> RwLockWriteGuard<'static, UserMap> {
USER_MAP
.get()
.expect("map is set")
.write()
.expect("not poisoned")
}

fn auth_map() -> RwLockReadGuard<'static, AuthMap> {
AUTH_MAP
.get()
.expect("map is set")
.read()
.expect("not poisoned")
}

fn mut_auth_map() -> RwLockWriteGuard<'static, AuthMap> {
AUTH_MAP
.get()
.expect("map is set")
.write()
.expect("not poisoned")
}
use crate::rbac::map::{auth_map, mut_auth_map, mut_user_map, user_map};
use crate::rbac::role::{model::DefaultPrivilege, Action};
use crate::rbac::user::User;

// This type encapsulates both the user_map and auth_map
// so other entities deal with only this type
pub struct Users;

impl Users {
Expand Down Expand Up @@ -122,13 +81,15 @@ impl Users {
return res;
}

// if not found in auth map, look into user map
let (username, password) = key;
// verify pass and add this user's perm to auth map
if let Some(user) = user_map().get(&username) {
// if user exists and password matches
// add this user to auth map
if user.verify_password(&password) {
let mut auth_map = mut_auth_map();
auth_map.add_user(username.clone(), password.clone(), user.permissions());
// verify auth and return
// verify from auth map and return
let key = (username, password);
return auth_map
.check_auth(&key, action, stream)
Expand All @@ -139,23 +100,3 @@ impl Users {
false
}
}

pub fn set_user_map(users: Vec<User>) {
let mut user_map = UserMap::from(users);
let mut auth_map = AuthMap::default();
let admin = get_admin_user();
let admin_permissions = admin.permissions();
user_map.insert(admin);
auth_map.add_user(
CONFIG.parseable.username.clone(),
CONFIG.parseable.password.clone(),
admin_permissions,
);

USER_MAP
.set(RwLock::new(user_map))
.expect("map is only set once");
AUTH_MAP
.set(RwLock::new(auth_map))
.expect("map is only set once");
}
65 changes: 0 additions & 65 deletions server/src/rbac/auth.rs

This file was deleted.

155 changes: 155 additions & 0 deletions server/src/rbac/map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Parseable Server (C) 2022 - 2023 Parseable, Inc.
*
* 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 crate::option::CONFIG;
use crate::rbac::user::User;
use std::collections::HashMap;

use super::{
role::{Action, Permission},
user,
};
use once_cell::sync::OnceCell;
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};

pub static USER_MAP: OnceCell<RwLock<UserMap>> = OnceCell::new();
pub static AUTH_MAP: OnceCell<RwLock<AuthMap>> = OnceCell::new();

pub fn user_map() -> RwLockReadGuard<'static, UserMap> {
USER_MAP
.get()
.expect("map is set")
.read()
.expect("not poisoned")
}

pub fn mut_user_map() -> RwLockWriteGuard<'static, UserMap> {
USER_MAP
.get()
.expect("map is set")
.write()
.expect("not poisoned")
}

pub fn auth_map() -> RwLockReadGuard<'static, AuthMap> {
AUTH_MAP
.get()
.expect("map is set")
.read()
.expect("not poisoned")
}

pub fn mut_auth_map() -> RwLockWriteGuard<'static, AuthMap> {
AUTH_MAP
.get()
.expect("map is set")
.write()
.expect("not poisoned")
}

// initialize the user and auth maps
// the user_map is initialized from the config file and has a list of all users
// the auth_map is initialized with admin user only and then gets lazily populated
// as users authenticate
pub fn init_auth_maps(users: Vec<User>) {
let mut user_map = UserMap::from(users);
let mut auth_map = AuthMap::default();
let admin = user::get_admin_user();
let admin_permissions = admin.permissions();
user_map.insert(admin);
auth_map.add_user(
CONFIG.parseable.username.clone(),
CONFIG.parseable.password.clone(),
admin_permissions,
);

USER_MAP
.set(RwLock::new(user_map))
.expect("map is only set once");
AUTH_MAP
.set(RwLock::new(auth_map))
.expect("map is only set once");
}

// AuthMap is a map of [(username, password) --> permissions]
// This map is populated lazily as users send auth requests.
// First auth request for a user will populate the map with
// the user info (password and permissions) and subsequent
// requests will check the map for the user.
// If user is present in the map then we use this map for both
// authentication and authorization.
#[derive(Debug, Default)]
pub struct AuthMap {
inner: HashMap<(String, String), Vec<Permission>>,
}

impl AuthMap {
pub fn add_user(&mut self, username: String, password: String, permissions: Vec<Permission>) {
self.inner.insert((username, password), permissions);
}

pub fn remove(&mut self, username: &str) {
self.inner.retain(|(x, _), _| x != username)
}

// returns None if user is not in the map
// Otherwise returns Some(is_authenticated)
pub fn check_auth(
&self,
key: &(String, String),
required_action: Action,
on_stream: Option<&str>,
) -> Option<bool> {
self.inner.get(key).map(|perms| {
perms.iter().any(|user_perm| {
match *user_perm {
// if any action is ALL then we we authorize
Permission::Unit(action) => action == required_action || action == Action::All,
Permission::Stream(action, ref stream) => {
let ok_stream = if let Some(on_stream) = on_stream {
stream == on_stream || stream == "*"
} else {
// if no stream to match then stream check is not needed
true
};
(action == required_action || action == Action::All) && ok_stream
}
}
})
})
}
}

// UserMap is a map of [username --> User]
// This map is populated at startup with the list of users from parseable.json file
#[derive(Debug, Default, Clone, derive_more::Deref, derive_more::DerefMut)]
pub struct UserMap(HashMap<String, User>);

impl UserMap {
pub fn insert(&mut self, user: User) {
self.0.insert(user.username.clone(), user);
}
}

impl From<Vec<User>> for UserMap {
fn from(users: Vec<User>) -> Self {
let mut map = Self::default();
map.extend(users.into_iter().map(|user| (user.username.clone(), user)));
map
}
}
Loading