diff --git a/src/api/v3/user/types.rs b/src/api/v3/user/types.rs index a15bdb0..481c489 100644 --- a/src/api/v3/user/types.rs +++ b/src/api/v3/user/types.rs @@ -353,7 +353,7 @@ impl From for identity_types::UserListParameters { domain_id: value.domain_id, name: value.name, unique_id: value.unique_id, - // limit: value.limit, + ..Default::default() // limit: value.limit, } } } diff --git a/src/identity/backends/sql.rs b/src/identity/backends/sql.rs index 9d19eba..d338c51 100644 --- a/src/identity/backends/sql.rs +++ b/src/identity/backends/sql.rs @@ -64,7 +64,13 @@ impl IdentityBackend for SqlBackend { state: &ServiceState, params: &UserListParameters, ) -> Result, IdentityProviderError> { - Ok(user::list(&self.config, &state.db, params).await?) + // Create a modified copy if user_type is None + let mut modified_params = params.clone(); + if modified_params.user_type.is_none() { + modified_params.user_type = Some(UserType::All); + } + + Ok(user::list(&self.config, &state.db, &modified_params).await?) } /// Get single user by ID diff --git a/src/identity/backends/sql/user/list.rs b/src/identity/backends/sql/user/list.rs index 01c7154..fc66916 100644 --- a/src/identity/backends/sql/user/list.rs +++ b/src/identity/backends/sql/user/list.rs @@ -35,93 +35,198 @@ pub async fn list( ) -> Result, IdentityDatabaseError> { // Prepare basic selects let mut user_select = DbUser::find(); - let mut local_user_select = LocalUser::find(); - let mut nonlocal_user_select = NonlocalUser::find(); - let mut federated_user_select = FederatedUser::find(); if let Some(domain_id) = ¶ms.domain_id { user_select = user_select.filter(db_user::Column::DomainId.eq(domain_id)); } - if let Some(name) = ¶ms.name { - local_user_select = local_user_select.filter(db_local_user::Column::Name.eq(name)); - nonlocal_user_select = nonlocal_user_select.filter(db_nonlocal_user::Column::Name.eq(name)); - federated_user_select = - federated_user_select.filter(db_federated_user::Column::DisplayName.eq(name)); + + let db_users = user_select.all(db).await.context("fetching users")?; + + if db_users.is_empty() { + return Ok(vec![]); } - let db_users: Vec = user_select.all(db).await.context("fetching users data")?; + let user_type = params.user_type.unwrap_or(UserType::All); + let last_activity_cutof_date = conf.get_user_last_activity_cutof_date(); - let (user_opts, local_users, nonlocal_users, federated_users) = tokio::join!( - db_users.load_many(UserOption, db), - db_users.load_one(local_user_select, db), - db_users.load_one(nonlocal_user_select, db), - db_users.load_many(federated_user_select, db) - ); + let mut results: Vec = Vec::new(); - let locals = local_users.context("fetching local users data")?; + match user_type { + UserType::Local => { + let mut local_user_select = LocalUser::find(); - let local_users_passwords: Vec>> = - local_user::load_local_users_passwords(db, locals.iter().cloned().map(|u| u.map(|x| x.id))) - .await?; + if let Some(name) = ¶ms.name { + local_user_select = local_user_select.filter(db_local_user::Column::Name.eq(name)); + } - let last_activity_cutof_date = conf.get_user_last_activity_cutof_date(); + let (user_opts, local_users) = tokio::join!( + db_users.load_many(UserOption, db), + db_users.load_one(local_user_select, db) + ); + let locals = local_users.context("fetching local users data")?; - let mut results: Vec = Vec::new(); - for (u, (o, (l, (p, (n, f))))) in db_users.into_iter().zip( - user_opts.context("fetching user options")?.into_iter().zip( - locals.into_iter().zip( - local_users_passwords.into_iter().zip( + let local_users_passwords: Vec>> = + local_user::load_local_users_passwords( + db, + locals.iter().cloned().map(|u| u.map(|x| x.id)), + ) + .await?; + + for ((u, opts), (l, p)) in db_users + .into_iter() + .zip(user_opts.context("fetching user options")?.into_iter()) + .zip(locals.into_iter().zip(local_users_passwords.into_iter())) + { + if let Some(local) = l { + let mut user_builder = UserResponseBuilder::default(); + user_builder.merge_user_data( + &u, + &UserOptions::from_iter(opts), + last_activity_cutof_date.as_ref(), + ); + user_builder.merge_local_user_data(&local); + if let Some(pass) = p { + user_builder.merge_passwords_data(pass.into_iter()); + } + results.push(user_builder.build()?); + } + } + } + UserType::NonLocal => { + let mut nonlocal_user_select = NonlocalUser::find(); + if let Some(name) = ¶ms.name { + nonlocal_user_select = + nonlocal_user_select.filter(db_nonlocal_user::Column::Name.eq(name)); + } + + let (user_opts, nonlocal_users) = tokio::join!( + db_users.load_many(UserOption, db), + db_users.load_one(nonlocal_user_select, db) + ); + + for ((u, opts), n) in db_users + .into_iter() + .zip(user_opts.context("fetching user options")?.into_iter()) + .zip( nonlocal_users .context("fetching nonlocal users data")? - .into_iter() - .zip( - federated_users - .context("fetching federated users data")? - .into_iter(), + .into_iter(), + ) + { + if let Some(nonlocal) = n { + let mut user_builder = UserResponseBuilder::default(); + user_builder.merge_user_data( + &u, + &UserOptions::from_iter(opts), + last_activity_cutof_date.as_ref(), + ); + user_builder.merge_nonlocal_user_data(&nonlocal); + results.push(user_builder.build()?); + } + } + } + UserType::Federated => { + let mut federated_user_select = db_federated_user::Entity::find(); + if let Some(name) = ¶ms.name { + federated_user_select = + federated_user_select.filter(db_federated_user::Column::DisplayName.eq(name)); + } + + let (user_opts, federated_users) = tokio::join!( + db_users.load_many(UserOption, db), + db_users.load_many(federated_user_select, db) + ); + for ((u, opts), f) in db_users + .into_iter() + .zip(user_opts.context("fetching user options")?.into_iter()) + .zip( + federated_users + .context("fetching federated users data")? + .into_iter(), + ) + { + if !f.is_empty() { + let mut user_builder = UserResponseBuilder::default(); + user_builder.merge_user_data( + &u, + &UserOptions::from_iter(opts), + last_activity_cutof_date.as_ref(), + ); + user_builder.merge_federated_user_data(f); + results.push(user_builder.build()?); + } + } + } + UserType::All => { + let mut local_user_select = LocalUser::find(); + let mut nonlocal_user_select = NonlocalUser::find(); + let mut federated_user_select = db_federated_user::Entity::find(); + if let Some(name) = ¶ms.name { + local_user_select = local_user_select.filter(db_local_user::Column::Name.eq(name)); + nonlocal_user_select = + nonlocal_user_select.filter(db_nonlocal_user::Column::Name.eq(name)); + federated_user_select = + federated_user_select.filter(db_federated_user::Column::DisplayName.eq(name)); + } + + let (user_opts, local_users, nonlocal_users, federated_users) = tokio::join!( + db_users.load_many(UserOption, db), + db_users.load_one(local_user_select, db), + db_users.load_one(nonlocal_user_select, db), + db_users.load_many(federated_user_select, db) + ); + + let locals = local_users.context("fetching local users data")?; + + let local_users_passwords: Vec>> = + local_user::load_local_users_passwords( + db, + locals.iter().cloned().map(|u| u.map(|x| x.id)), + ) + .await?; + + for (u, (o, (l, (p, (n, f))))) in db_users.into_iter().zip( + user_opts.context("fetching user options")?.into_iter().zip( + locals.into_iter().zip( + local_users_passwords.into_iter().zip( + nonlocal_users + .context("fetching nonlocal users data")? + .into_iter() + .zip( + federated_users + .context("fetching federated users data")? + .into_iter(), + ), ), + ), ), - ), - ), - ) { - if l.is_none() && n.is_none() && f.is_empty() { - continue; - } + ) { + if l.is_none() && n.is_none() && f.is_empty() { + continue; + } - let mut user_builder = UserResponseBuilder::default(); - user_builder.merge_user_data( - &u, - &UserOptions::from_iter(o), - last_activity_cutof_date.as_ref(), - ); - //user_builder.merge_options(&UserOptions::from_iter(o)); - if let Some(local) = l { - user_builder.merge_local_user_data(&local); - if let Some(pass) = p { - user_builder.merge_passwords_data(pass.into_iter()); + let mut user_builder = UserResponseBuilder::default(); + user_builder.merge_user_data( + &u, + &UserOptions::from_iter(o), + last_activity_cutof_date.as_ref(), + ); + //user_builder.merge_options(&UserOptions::from_iter(o)); + if let Some(local) = l { + user_builder.merge_local_user_data(&local); + if let Some(pass) = p { + user_builder.merge_passwords_data(pass.into_iter()); + } + } else if let Some(nonlocal) = n { + user_builder.merge_nonlocal_user_data(&nonlocal); + } else if !f.is_empty() { + user_builder.merge_federated_user_data(f); + } else { + return Err(IdentityDatabaseError::MalformedUser(u.id))?; + }; + results.push(user_builder.build()?); } - } else if let Some(nonlocal) = n { - user_builder.merge_nonlocal_user_data(&nonlocal); - } else if !f.is_empty() { - user_builder.merge_federated_user_data(f); - } else { - return Err(IdentityDatabaseError::MalformedUser(u.id))?; - }; - results.push(user_builder.build()?); + } } - - //let select: Vec<(String, Option, )> = DbUser::find() - //let select = DbUser::find(); - //let select = Prefixer::new(DbUser::find().select_only()) - // .add_columns(DbUser) - // .add_columns(LocalUser) - // .add_columns(NonlocalUser) - // .selector - // .left_join(LocalUser) - // .left_join(NonlocalUser) - // //.left_join(FederatedUser) - // .into_model::() - // .all(db) - // .await - // .unwrap(); Ok(results) } diff --git a/src/identity/types/user.rs b/src/identity/types/user.rs index a65db7e..07cd97e 100644 --- a/src/identity/types/user.rs +++ b/src/identity/types/user.rs @@ -204,6 +204,28 @@ pub struct UserListParameters { #[builder(default)] #[validate(length(max = 64))] pub unique_id: Option, + /// Filter users by User Type (local, federated, nonlocal, all). + #[builder(default)] + #[serde(default, rename = "type")] + pub user_type: Option, +} + +/// User type for filtering. +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Hash, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum UserType { + /// Local users only (with passwords). + Local, + + /// Federated users only (authenticated via external IdP). + Federated, + + /// Non-local users (users without local authentication). + NonLocal, + + /// All users (default behavior). + #[default] + All, } /// User password information.