Skip to content

Commit ca1f427

Browse files
authored
Unify RBAC flow in a single module (#426)
This commit combines all the RBAC specific code in a single crate. This improves readability. Also added more relevant comments
1 parent e47b9b0 commit ca1f427

File tree

7 files changed

+175
-161
lines changed

7 files changed

+175
-161
lines changed

server/src/handlers/http/middleware.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@ use actix_web::{
2727
use actix_web_httpauth::extractors::basic::BasicAuth;
2828
use futures_util::future::LocalBoxFuture;
2929

30-
use crate::{
31-
option::CONFIG,
32-
rbac::{role::Action, Users},
33-
};
30+
use crate::{option::CONFIG, rbac::role::Action, rbac::Users};
3431

3532
pub struct Auth {
3633
pub action: Action,
@@ -77,7 +74,7 @@ where
7774
forward_ready!(service);
7875

7976
fn call(&self, mut req: ServiceRequest) -> Self::Future {
80-
// Extract username and password using basic auth extractor.
77+
// Extract username and password from the request using basic auth extractor.
8178
let creds = req.extract::<BasicAuth>().into_inner();
8279
let creds = creds.map_err(Into::into).map(|creds| {
8380
let username = creds.user_id().trim().to_owned();

server/src/handlers/http/rbac.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub async fn delete_user(username: web::Path<String>) -> Result<impl Responder,
9696
// Reset password for given username
9797
// returns new password generated for this user
9898
pub async fn reset_password(username: String) -> Result<String, RBACError> {
99-
// get new password for this user
99+
// generate new password for this user
100100
let PassCode { password, hash } = User::gen_new_password();
101101
// update parseable.json first
102102
let mut metadata = get_metadata().await?;
@@ -145,7 +145,7 @@ pub async fn put_role(
145145
put_metadata(&metadata).await?;
146146
// update in mem table
147147
Users.put_role(&username, role);
148-
Ok(format!("Roles updated successfully for {}", username))
148+
Ok(format!("Roles updated successfully for {username}"))
149149
}
150150

151151
async fn get_metadata() -> Result<crate::storage::StorageMetadata, ObjectStorageError> {

server/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ async fn main() -> anyhow::Result<()> {
6464
migration::run_metadata_migration(&CONFIG).await?;
6565
let metadata = storage::resolve_parseable_metadata().await?;
6666
banner::print(&CONFIG, &metadata).await;
67-
rbac::set_user_map(metadata.users.clone());
67+
rbac::map::init_auth_maps(metadata.users.clone());
6868
metadata.set_global();
6969
let prometheus = metrics::build_metrics_handler();
7070
CONFIG.storage().register_store_metrics(&prometheus);

server/src/rbac.rs

Lines changed: 10 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,57 +16,16 @@
1616
*
1717
*/
1818

19-
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
20-
21-
use once_cell::sync::OnceCell;
22-
23-
use crate::option::CONFIG;
24-
25-
use self::{
26-
auth::AuthMap,
27-
role::{model::DefaultPrivilege, Action},
28-
user::{get_admin_user, User, UserMap},
29-
};
30-
31-
pub mod auth;
19+
pub mod map;
3220
pub mod role;
3321
pub mod user;
3422

35-
pub static USER_MAP: OnceCell<RwLock<UserMap>> = OnceCell::new();
36-
pub static AUTH_MAP: OnceCell<RwLock<AuthMap>> = OnceCell::new();
37-
38-
fn user_map() -> RwLockReadGuard<'static, UserMap> {
39-
USER_MAP
40-
.get()
41-
.expect("map is set")
42-
.read()
43-
.expect("not poisoned")
44-
}
45-
46-
fn mut_user_map() -> RwLockWriteGuard<'static, UserMap> {
47-
USER_MAP
48-
.get()
49-
.expect("map is set")
50-
.write()
51-
.expect("not poisoned")
52-
}
53-
54-
fn auth_map() -> RwLockReadGuard<'static, AuthMap> {
55-
AUTH_MAP
56-
.get()
57-
.expect("map is set")
58-
.read()
59-
.expect("not poisoned")
60-
}
61-
62-
fn mut_auth_map() -> RwLockWriteGuard<'static, AuthMap> {
63-
AUTH_MAP
64-
.get()
65-
.expect("map is set")
66-
.write()
67-
.expect("not poisoned")
68-
}
23+
use crate::rbac::map::{auth_map, mut_auth_map, mut_user_map, user_map};
24+
use crate::rbac::role::{model::DefaultPrivilege, Action};
25+
use crate::rbac::user::User;
6926

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

7231
impl Users {
@@ -122,13 +81,15 @@ impl Users {
12281
return res;
12382
}
12483

84+
// if not found in auth map, look into user map
12585
let (username, password) = key;
126-
// verify pass and add this user's perm to auth map
12786
if let Some(user) = user_map().get(&username) {
87+
// if user exists and password matches
88+
// add this user to auth map
12889
if user.verify_password(&password) {
12990
let mut auth_map = mut_auth_map();
13091
auth_map.add_user(username.clone(), password.clone(), user.permissions());
131-
// verify auth and return
92+
// verify from auth map and return
13293
let key = (username, password);
13394
return auth_map
13495
.check_auth(&key, action, stream)
@@ -139,23 +100,3 @@ impl Users {
139100
false
140101
}
141102
}
142-
143-
pub fn set_user_map(users: Vec<User>) {
144-
let mut user_map = UserMap::from(users);
145-
let mut auth_map = AuthMap::default();
146-
let admin = get_admin_user();
147-
let admin_permissions = admin.permissions();
148-
user_map.insert(admin);
149-
auth_map.add_user(
150-
CONFIG.parseable.username.clone(),
151-
CONFIG.parseable.password.clone(),
152-
admin_permissions,
153-
);
154-
155-
USER_MAP
156-
.set(RwLock::new(user_map))
157-
.expect("map is only set once");
158-
AUTH_MAP
159-
.set(RwLock::new(auth_map))
160-
.expect("map is only set once");
161-
}

server/src/rbac/auth.rs

Lines changed: 0 additions & 65 deletions
This file was deleted.

server/src/rbac/map.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Parseable Server (C) 2022 - 2023 Parseable, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as
6+
* published by the Free Software Foundation, either version 3 of the
7+
* License, or (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*
17+
*/
18+
19+
use crate::option::CONFIG;
20+
use crate::rbac::user::User;
21+
use std::collections::HashMap;
22+
23+
use super::{
24+
role::{Action, Permission},
25+
user,
26+
};
27+
use once_cell::sync::OnceCell;
28+
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
29+
30+
pub static USER_MAP: OnceCell<RwLock<UserMap>> = OnceCell::new();
31+
pub static AUTH_MAP: OnceCell<RwLock<AuthMap>> = OnceCell::new();
32+
33+
pub fn user_map() -> RwLockReadGuard<'static, UserMap> {
34+
USER_MAP
35+
.get()
36+
.expect("map is set")
37+
.read()
38+
.expect("not poisoned")
39+
}
40+
41+
pub fn mut_user_map() -> RwLockWriteGuard<'static, UserMap> {
42+
USER_MAP
43+
.get()
44+
.expect("map is set")
45+
.write()
46+
.expect("not poisoned")
47+
}
48+
49+
pub fn auth_map() -> RwLockReadGuard<'static, AuthMap> {
50+
AUTH_MAP
51+
.get()
52+
.expect("map is set")
53+
.read()
54+
.expect("not poisoned")
55+
}
56+
57+
pub fn mut_auth_map() -> RwLockWriteGuard<'static, AuthMap> {
58+
AUTH_MAP
59+
.get()
60+
.expect("map is set")
61+
.write()
62+
.expect("not poisoned")
63+
}
64+
65+
// initialize the user and auth maps
66+
// the user_map is initialized from the config file and has a list of all users
67+
// the auth_map is initialized with admin user only and then gets lazily populated
68+
// as users authenticate
69+
pub fn init_auth_maps(users: Vec<User>) {
70+
let mut user_map = UserMap::from(users);
71+
let mut auth_map = AuthMap::default();
72+
let admin = user::get_admin_user();
73+
let admin_permissions = admin.permissions();
74+
user_map.insert(admin);
75+
auth_map.add_user(
76+
CONFIG.parseable.username.clone(),
77+
CONFIG.parseable.password.clone(),
78+
admin_permissions,
79+
);
80+
81+
USER_MAP
82+
.set(RwLock::new(user_map))
83+
.expect("map is only set once");
84+
AUTH_MAP
85+
.set(RwLock::new(auth_map))
86+
.expect("map is only set once");
87+
}
88+
89+
// AuthMap is a map of [(username, password) --> permissions]
90+
// This map is populated lazily as users send auth requests.
91+
// First auth request for a user will populate the map with
92+
// the user info (password and permissions) and subsequent
93+
// requests will check the map for the user.
94+
// If user is present in the map then we use this map for both
95+
// authentication and authorization.
96+
#[derive(Debug, Default)]
97+
pub struct AuthMap {
98+
inner: HashMap<(String, String), Vec<Permission>>,
99+
}
100+
101+
impl AuthMap {
102+
pub fn add_user(&mut self, username: String, password: String, permissions: Vec<Permission>) {
103+
self.inner.insert((username, password), permissions);
104+
}
105+
106+
pub fn remove(&mut self, username: &str) {
107+
self.inner.retain(|(x, _), _| x != username)
108+
}
109+
110+
// returns None if user is not in the map
111+
// Otherwise returns Some(is_authenticated)
112+
pub fn check_auth(
113+
&self,
114+
key: &(String, String),
115+
required_action: Action,
116+
on_stream: Option<&str>,
117+
) -> Option<bool> {
118+
self.inner.get(key).map(|perms| {
119+
perms.iter().any(|user_perm| {
120+
match *user_perm {
121+
// if any action is ALL then we we authorize
122+
Permission::Unit(action) => action == required_action || action == Action::All,
123+
Permission::Stream(action, ref stream) => {
124+
let ok_stream = if let Some(on_stream) = on_stream {
125+
stream == on_stream || stream == "*"
126+
} else {
127+
// if no stream to match then stream check is not needed
128+
true
129+
};
130+
(action == required_action || action == Action::All) && ok_stream
131+
}
132+
}
133+
})
134+
})
135+
}
136+
}
137+
138+
// UserMap is a map of [username --> User]
139+
// This map is populated at startup with the list of users from parseable.json file
140+
#[derive(Debug, Default, Clone, derive_more::Deref, derive_more::DerefMut)]
141+
pub struct UserMap(HashMap<String, User>);
142+
143+
impl UserMap {
144+
pub fn insert(&mut self, user: User) {
145+
self.0.insert(user.username.clone(), user);
146+
}
147+
}
148+
149+
impl From<Vec<User>> for UserMap {
150+
fn from(users: Vec<User>) -> Self {
151+
let mut map = Self::default();
152+
map.extend(users.into_iter().map(|user| (user.username.clone(), user)));
153+
map
154+
}
155+
}

0 commit comments

Comments
 (0)