Skip to content

Commit 2d8c655

Browse files
fix: use userid, username and method in a usergroup
new structure for POST /usergroup ``` { "name": "parseable", "roles": [], "users": [ { "username": "nikhil", "method": "native" } ] } ``` new structure for add/remove user - PATCH /usergroup/{name}/add(remove) ``` { "users": [ { "username": "authentik Default Admin", "method": "oauth" } ] } ```
1 parent f10b28a commit 2d8c655

File tree

7 files changed

+129
-43
lines changed

7 files changed

+129
-43
lines changed

src/handlers/http/modal/ingest/ingestor_role.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ pub async fn put(
4848
// refresh the sessions of all users using this role
4949
// for this, iterate over all user_groups and users and create a hashset of users
5050
let mut session_refresh_users: HashSet<String> = HashSet::new();
51-
for user_group in read_user_groups().values().cloned() {
51+
for user_group in read_user_groups().values() {
5252
if user_group.roles.contains(&name) {
53-
session_refresh_users.extend(user_group.users);
53+
session_refresh_users.extend(user_group.get_usernames());
5454
}
5555
}
5656

5757
// iterate over all users to see if they have this role
58-
for user in users().values().cloned() {
58+
for user in users().values() {
5959
if user.roles.contains(&name) {
6060
session_refresh_users.insert(user.username().to_string());
6161
}

src/handlers/http/modal/query/querier_rbac.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub async fn post_user(
103103
Ok(password)
104104
}
105105

106-
// Handler for DELETE /api/v1/user/delete/{username}
106+
// Handler for DELETE /api/v1/user/{username}
107107
pub async fn delete_user(username: web::Path<String>) -> Result<impl Responder, RBACError> {
108108
let username = username.into_inner();
109109
let _ = UPDATE_LOCK.lock().await;
@@ -120,19 +120,25 @@ pub async fn delete_user(username: web::Path<String>) -> Result<impl Responder,
120120
let mut groups_to_update = Vec::new();
121121
for user_group in user_groups {
122122
if let Some(ug) = write_user_groups().get_mut(&user_group) {
123-
ug.remove_users(HashSet::from_iter([username.clone()]))?;
123+
ug.remove_users_by_user_ids(HashSet::from_iter([username.clone()]))?;
124124
groups_to_update.push(ug.clone());
125-
// ug.update_in_metadata().await?;
126125
} else {
127126
continue;
128127
};
129128
}
130129

131-
// update in metadata user group
132-
metadata
133-
.user_groups
134-
.retain(|x| !groups_to_update.contains(x));
135-
metadata.user_groups.extend(groups_to_update);
130+
// For each updated group, replace in place if found; otherwise push
131+
for updated_group in &groups_to_update {
132+
if let Some(existing) = metadata
133+
.user_groups
134+
.iter_mut()
135+
.find(|ug| ug.name == updated_group.name)
136+
{
137+
*existing = updated_group.clone();
138+
} else {
139+
metadata.user_groups.push(updated_group.clone());
140+
}
141+
}
136142
put_metadata(&metadata).await?;
137143

138144
sync_user_deletion_with_ingestors(&username).await?;

src/handlers/http/modal/query/querier_role.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ pub async fn put(
5151
// refresh the sessions of all users using this role
5252
// for this, iterate over all user_groups and users and create a hashset of users
5353
let mut session_refresh_users: HashSet<String> = HashSet::new();
54-
for user_group in read_user_groups().values().cloned() {
54+
for user_group in read_user_groups().values() {
5555
if user_group.roles.contains(&name) {
56-
session_refresh_users.extend(user_group.users);
56+
session_refresh_users.extend(user_group.get_usernames());
5757
}
5858
}
5959

6060
// iterate over all users to see if they have this role
61-
for user in users().values().cloned() {
61+
for user in users().values() {
6262
if user.roles.contains(&name) {
6363
session_refresh_users.insert(user.username().to_string());
6464
}

src/handlers/http/oidc.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use crate::{
3838
rbac::{
3939
self, Users,
4040
map::{DEFAULT_ROLE, SessionKey},
41-
user::{self, User, UserType},
41+
user::{self, GroupUser, User, UserType},
4242
},
4343
storage::{self, ObjectStorageError, StorageMetadata},
4444
utils::actix::extract_session_key_from_req,
@@ -432,12 +432,11 @@ pub async fn update_user_if_changed(
432432
entry.clone_from(&user);
433433
// migrate user references inside user groups
434434
for group in metadata.user_groups.iter_mut() {
435-
if group.users.remove(&old_username) {
436-
group.users.insert(user.username().to_string());
437-
}
435+
group.users.retain(|u| u.userid() != old_username);
436+
group.users.insert(GroupUser::from_user(&user));
438437
}
439-
put_metadata(&metadata).await?;
440438
}
439+
put_metadata(&metadata).await?;
441440
Users.delete_user(&old_username);
442441
Users.put_user(user.clone());
443442
Ok(user)

src/handlers/http/rbac.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use tokio::sync::Mutex;
4242

4343
use super::modal::utils::rbac_utils::{get_metadata, put_metadata};
4444

45-
// async aware lock for updating storage metadata and user map atomicically
45+
// async aware lock for updating storage metadata and user map atomically
4646
static UPDATE_LOCK: Mutex<()> = Mutex::const_new(());
4747

4848
#[derive(serde::Serialize)]
@@ -66,13 +66,13 @@ impl From<&user::User> for User {
6666
}
6767

6868
// Handler for GET /api/v1/user
69-
// returns list of all registerd users
69+
// returns list of all registered users
7070
pub async fn list_users() -> impl Responder {
7171
web::Json(Users.collect_user::<User>())
7272
}
7373

7474
/// Handler for GET /api/v1/users
75-
/// returns list of all registerd users along with their roles and other info
75+
/// returns list of all registered users along with their roles and other info
7676
pub async fn list_users_prism() -> impl Responder {
7777
// get all users
7878
let prism_users = rbac::map::users().values().map(to_prism_user).collect_vec();
@@ -350,7 +350,7 @@ pub async fn remove_roles_from_user(
350350
}
351351

352352
#[derive(Debug, Serialize)]
353-
#[serde(rename = "camelCase")]
353+
#[serde(rename_all = "camelCase")]
354354
pub struct InvalidUserGroupError {
355355
pub valid_name: bool,
356356
pub non_existent_roles: Vec<String>,
@@ -390,7 +390,7 @@ pub enum RBACError {
390390
InvalidUserGroupRequest(Box<InvalidUserGroupError>),
391391
#[error("{0}")]
392392
InvalidSyncOperation(String),
393-
#[error("User group `{0}` is still being used")]
393+
#[error("UserGroup `{0}` is still in use")]
394394
UserGroupNotEmpty(String),
395395
#[error("Resource `{0}` is still in use")]
396396
ResourceInUse(String),

src/handlers/http/role.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ pub async fn put(
5050
// refresh the sessions of all users using this role
5151
// for this, iterate over all user_groups and users and create a hashset of users
5252
let mut session_refresh_users: HashSet<String> = HashSet::new();
53-
for user_group in read_user_groups().values().cloned() {
53+
for user_group in read_user_groups().values() {
5454
if user_group.roles.contains(&name) {
55-
session_refresh_users.extend(user_group.users);
55+
session_refresh_users.extend(user_group.get_usernames());
5656
}
5757
}
5858

5959
// iterate over all users to see if they have this role
60-
for user in users().values().cloned() {
60+
for user in users().values() {
6161
if user.roles.contains(&name) {
6262
session_refresh_users.insert(user.username().to_string());
6363
}

src/rbac/user.rs

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,56 @@ impl From<openid::Userinfo> for UserInfo {
203203
}
204204
}
205205

206+
/// Represents a user in a UserGroup - simplified structure for both user types
207+
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
208+
pub struct GroupUser {
209+
pub userid: String,
210+
pub username: String,
211+
pub method: String,
212+
}
213+
214+
impl GroupUser {
215+
pub fn username(&self) -> &str {
216+
&self.username
217+
}
218+
219+
pub fn userid(&self) -> &str {
220+
&self.userid
221+
}
222+
223+
pub fn is_oauth(&self) -> bool {
224+
self.method == "oauth"
225+
}
226+
227+
pub fn user_type(&self) -> &str {
228+
if self.is_oauth() { "oauth" } else { "native" }
229+
}
230+
231+
pub fn from_user(user: &User) -> Self {
232+
match &user.ty {
233+
UserType::Native(Basic { username, .. }) => GroupUser {
234+
userid: username.clone(), // Same value for basic users
235+
username: username.clone(),
236+
method: "native".to_string(),
237+
},
238+
UserType::OAuth(OAuth { userid, user_info }) => {
239+
// For OAuth users, derive the display username from user_info
240+
let display_username = user_info
241+
.name
242+
.clone()
243+
.or_else(|| user_info.email.clone())
244+
.unwrap_or_else(|| userid.clone()); // fallback to userid if nothing else available
245+
246+
GroupUser {
247+
userid: userid.clone(),
248+
username: display_username,
249+
method: "oauth".to_string(),
250+
}
251+
}
252+
}
253+
}
254+
}
255+
206256
/// Logically speaking, UserGroup is a collection of roles and is applied to a collection of users.
207257
///
208258
/// The users present in a group inherit all the roles present in the group for as long as they are a part of the group.
@@ -212,7 +262,7 @@ pub struct UserGroup {
212262
// #[serde(default = "crate::utils::uid::gen")]
213263
// pub id: Ulid,
214264
pub roles: HashSet<String>,
215-
pub users: HashSet<String>,
265+
pub users: HashSet<GroupUser>,
216266
}
217267

218268
fn is_valid_group_name(name: &str) -> bool {
@@ -239,9 +289,9 @@ impl UserGroup {
239289
let mut non_existent_users = Vec::new();
240290
if !self.users.is_empty() {
241291
// validate that the users exist
242-
for user in &self.users {
243-
if !users().contains_key(user) {
244-
non_existent_users.push(user.clone());
292+
for group_user in &self.users {
293+
if !users().contains_key(group_user.userid()) {
294+
non_existent_users.push(group_user.username().to_string());
245295
}
246296
}
247297
}
@@ -266,7 +316,7 @@ impl UserGroup {
266316
Ok(())
267317
}
268318
}
269-
pub fn new(name: String, roles: HashSet<String>, users: HashSet<String>) -> Self {
319+
pub fn new(name: String, roles: HashSet<String>, users: HashSet<GroupUser>) -> Self {
270320
UserGroup { name, roles, users }
271321
}
272322

@@ -276,20 +326,20 @@ impl UserGroup {
276326
}
277327
self.roles.extend(roles);
278328
// also refresh all user sessions
279-
for username in &self.users {
280-
mut_sessions().remove_user(username);
329+
for group_user in &self.users {
330+
mut_sessions().remove_user(group_user.userid());
281331
}
282332
Ok(())
283333
}
284334

285-
pub fn add_users(&mut self, users: HashSet<String>) -> Result<(), RBACError> {
335+
pub fn add_users(&mut self, users: HashSet<GroupUser>) -> Result<(), RBACError> {
286336
if users.is_empty() {
287337
return Ok(());
288338
}
289339
self.users.extend(users.clone());
290340
// also refresh all user sessions
291-
for username in &users {
292-
mut_sessions().remove_user(username);
341+
for group_user in &users {
342+
mut_sessions().remove_user(group_user.userid());
293343
}
294344
Ok(())
295345
}
@@ -307,30 +357,61 @@ impl UserGroup {
307357
self.roles.clone_from(&new_roles);
308358

309359
// also refresh all user sessions
310-
for username in &self.users {
311-
mut_sessions().remove_user(username);
360+
for group_user in &self.users {
361+
mut_sessions().remove_user(group_user.userid());
312362
}
313363
Ok(())
314364
}
315365

316-
pub fn remove_users(&mut self, users: HashSet<String>) -> Result<(), RBACError> {
366+
pub fn remove_users(&mut self, users: HashSet<GroupUser>) -> Result<(), RBACError> {
317367
if users.is_empty() {
318368
return Ok(());
319369
}
320370
let new_users = HashSet::from_iter(self.users.difference(&users).cloned());
321-
let removed_users: HashSet<String> = self.users.intersection(&users).cloned().collect();
371+
let removed_users: HashSet<GroupUser> = self.users.intersection(&users).cloned().collect();
322372
if removed_users.is_empty() {
323373
return Ok(());
324374
}
325375
// also refresh all user sessions
326-
for username in &removed_users {
327-
mut_sessions().remove_user(username);
376+
for group_user in &removed_users {
377+
mut_sessions().remove_user(group_user.userid());
328378
}
329379
self.users.clone_from(&new_users);
330380

331381
Ok(())
332382
}
333383

384+
/// Get all usernames in this group
385+
pub fn get_usernames(&self) -> Vec<String> {
386+
self.users
387+
.iter()
388+
.map(|u| u.username().to_string())
389+
.collect()
390+
}
391+
392+
/// Add users by converting from User references
393+
pub fn add_users_from_user_refs(&mut self, user_refs: &[&User]) -> Result<(), RBACError> {
394+
let group_users: HashSet<GroupUser> =
395+
user_refs.iter().map(|u| GroupUser::from_user(u)).collect();
396+
self.add_users(group_users)
397+
}
398+
399+
/// Remove users by user ID
400+
pub fn remove_users_by_user_ids(&mut self, user_ids: HashSet<String>) -> Result<(), RBACError> {
401+
if user_ids.is_empty() {
402+
return Ok(());
403+
}
404+
405+
let users_to_remove: HashSet<GroupUser> = self
406+
.users
407+
.iter()
408+
.filter(|u| user_ids.contains(u.userid()))
409+
.cloned()
410+
.collect();
411+
412+
self.remove_users(users_to_remove)
413+
}
414+
334415
pub async fn update_in_metadata(&self) -> Result<(), RBACError> {
335416
let mut metadata = get_metadata().await?;
336417
metadata.user_groups.retain(|x| x.name != self.name);

0 commit comments

Comments
 (0)