From 1a1c340360a95bc4a7748c5670f524f308420d31 Mon Sep 17 00:00:00 2001 From: Satyam Singh Date: Wed, 27 Sep 2023 15:29:59 +0530 Subject: [PATCH] add default role api (#517) This PR adds an API to set default role to be assigned to any new OIDC user. This is needed for identity providers like Google where there is no group membership for a given user. Fixes #513 --- server/src/handlers/http.rs | 6 ++++++ server/src/handlers/http/oidc.rs | 29 ++++++++++++++++------------ server/src/handlers/http/role.rs | 27 +++++++++++++++++++++++++- server/src/main.rs | 2 +- server/src/rbac/map.rs | 14 ++++++++++---- server/src/rbac/user.rs | 4 ++-- server/src/storage/store_metadata.rs | 3 +++ 7 files changed, 65 insertions(+), 20 deletions(-) diff --git a/server/src/handlers/http.rs b/server/src/handlers/http.rs index ce6023188..832f5ea69 100644 --- a/server/src/handlers/http.rs +++ b/server/src/handlers/http.rs @@ -261,8 +261,14 @@ pub fn configure_routes( .authorize(Action::QueryLLM), ), ); + let role_api = web::scope("/role") .service(resource("").route(web::get().to(role::list).authorize(Action::ListRole))) + .service( + resource("/default") + .route(web::put().to(role::put_default).authorize(Action::PutRole)) + .route(web::get().to(role::get_default).authorize(Action::GetRole)), + ) .service( resource("/{name}") .route(web::put().to(role::put).authorize(Action::PutRole)) diff --git a/server/src/handlers/http/oidc.rs b/server/src/handlers/http/oidc.rs index 1cce3819d..618805762 100644 --- a/server/src/handlers/http/oidc.rs +++ b/server/src/handlers/http/oidc.rs @@ -34,7 +34,7 @@ use crate::{ oidc::{Claims, DiscoveredClient}, option::CONFIG, rbac::{ - map::SessionKey, + map::{SessionKey, DEFAULT_ROLE}, user::{User, UserType}, Users, }, @@ -263,22 +263,27 @@ async fn put_user( group: Option>, ) -> Result { let mut metadata = get_metadata().await?; - let user = match metadata + let group = group.unwrap_or_else(|| { + DEFAULT_ROLE + .lock() + .unwrap() + .clone() + .map(|role| HashSet::from([role])) + .unwrap_or_default() + }); + + let user = metadata .users .iter() .find(|user| user.username() == username) - { - Some(user) => user.clone(), - None => { - let mut user = User::new_oauth(username.to_owned()); - if let Some(group) = group { - user.roles = group - } + .cloned() + .unwrap_or_else(|| { + let user = User::new_oauth(username.to_owned(), group); metadata.users.push(user.clone()); - put_metadata(&metadata).await?; user - } - }; + }); + + put_metadata(&metadata).await?; Users.put_user(user.clone()); Ok(user) } diff --git a/server/src/handlers/http/role.rs b/server/src/handlers/http/role.rs index 9052e2ec4..670635707 100644 --- a/server/src/handlers/http/role.rs +++ b/server/src/handlers/http/role.rs @@ -21,7 +21,10 @@ use http::StatusCode; use crate::{ option::CONFIG, - rbac::{map::mut_roles, role::model::DefaultPrivilege}, + rbac::{ + map::{mut_roles, DEFAULT_ROLE}, + role::model::DefaultPrivilege, + }, storage::{self, ObjectStorageError, StorageMetadata}, }; @@ -71,6 +74,28 @@ pub async fn delete(name: web::Path) -> Result) -> Result { + let name = name.into_inner(); + let mut metadata = get_metadata().await?; + metadata.default_role = Some(name.clone()); + *DEFAULT_ROLE.lock().unwrap() = Some(name); + put_metadata(&metadata).await?; + Ok(HttpResponse::Ok().finish()) +} + +// Handler for GET /api/v1/role/default +// Delete existing role +pub async fn get_default() -> Result { + let res = match DEFAULT_ROLE.lock().unwrap().clone() { + Some(role) => serde_json::Value::String(role), + None => serde_json::Value::Null, + }; + + Ok(web::Json(res)) +} + async fn get_metadata() -> Result { let metadata = CONFIG .storage() diff --git a/server/src/main.rs b/server/src/main.rs index 2b35e7d6c..4077236e7 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -65,7 +65,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::map::init(metadata.users.clone(), metadata.roles.clone()); + rbac::map::init(&metadata); metadata.set_global(); let prometheus = metrics::build_metrics_handler(); CONFIG.storage().register_store_metrics(&prometheus); diff --git a/server/src/rbac/map.rs b/server/src/rbac/map.rs index 17fcc7531..13d558829 100644 --- a/server/src/rbac/map.rs +++ b/server/src/rbac/map.rs @@ -16,22 +16,23 @@ * */ -use crate::option::CONFIG; use crate::rbac::user::User; -use std::collections::HashMap; +use crate::{option::CONFIG, storage::StorageMetadata}; +use std::{collections::HashMap, sync::Mutex}; use super::{ role::{model::DefaultPrivilege, Action, Permission, RoleBuilder}, user, }; use chrono::{DateTime, Utc}; -use once_cell::sync::OnceCell; +use once_cell::sync::{Lazy, OnceCell}; use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; pub type Roles = HashMap>; pub static USERS: OnceCell> = OnceCell::new(); pub static ROLES: OnceCell> = OnceCell::new(); +pub static DEFAULT_ROLE: Lazy>> = Lazy::new(|| Mutex::new(None)); pub static SESSIONS: OnceCell> = OnceCell::new(); pub fn users() -> RwLockReadGuard<'static, Users> { @@ -86,7 +87,12 @@ pub fn mut_sessions() -> RwLockWriteGuard<'static, Sessions> { // 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(users: Vec, mut roles: Roles) { +pub fn init(metadata: &StorageMetadata) { + let users = metadata.users.clone(); + let mut roles = metadata.roles.clone(); + + *DEFAULT_ROLE.lock().unwrap() = metadata.default_role.clone(); + let admin_privilege = DefaultPrivilege::Admin; let admin_permissions = RoleBuilder::from(&admin_privilege).build(); roles.insert("admin".to_string(), vec![admin_privilege]); diff --git a/server/src/rbac/user.rs b/server/src/rbac/user.rs index c799bef75..533976012 100644 --- a/server/src/rbac/user.rs +++ b/server/src/rbac/user.rs @@ -57,10 +57,10 @@ impl User { ) } - pub fn new_oauth(username: String) -> Self { + pub fn new_oauth(username: String, roles: HashSet) -> Self { Self { ty: UserType::OAuth(OAuth { userid: username }), - roles: HashSet::new(), + roles, } } diff --git a/server/src/storage/store_metadata.rs b/server/src/storage/store_metadata.rs index 7ad461fb9..b47aa3d95 100644 --- a/server/src/storage/store_metadata.rs +++ b/server/src/storage/store_metadata.rs @@ -57,6 +57,8 @@ pub struct StorageMetadata { pub streams: Vec, #[serde(default)] pub roles: HashMap>, + #[serde(default)] + pub default_role: Option, } impl StorageMetadata { @@ -70,6 +72,7 @@ impl StorageMetadata { users: Vec::new(), streams: Vec::new(), roles: HashMap::default(), + default_role: None, } }