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, } }