Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/v3/user/types.rs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fine in the concept, but we are not going to expose this in the api v3 (so far). I suggest first you focus only on the backend/provider part. In the separate PR you can then start relying on that, but only in the v4 api (or at least we can have a separate discussion in the dedicated PR)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove this part of the change - no api changes for now

Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ impl From<UserListParameters> 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,
}
}
}
8 changes: 7 additions & 1 deletion src/identity/backends/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ impl IdentityBackend for SqlBackend {
state: &ServiceState,
params: &UserListParameters,
) -> Result<Vec<UserResponse>, 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
Expand Down
245 changes: 175 additions & 70 deletions src/identity/backends/sql/user/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,93 +35,198 @@ pub async fn list(
) -> Result<Vec<UserResponse>, 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) = &params.domain_id {
user_select = user_select.filter(db_user::Column::DomainId.eq(domain_id));
}
if let Some(name) = &params.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<db_user::Model> = 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<UserResponse> = 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<Option<Vec<db_password::Model>>> =
local_user::load_local_users_passwords(db, locals.iter().cloned().map(|u| u.map(|x| x.id)))
.await?;
if let Some(name) = &params.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<UserResponse> = 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<Option<Vec<db_password::Model>>> =
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) = &params.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) = &params.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) = &params.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<Option<Vec<db_password::Model>>> =
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<String>, )> = 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::<DbUserData>()
// .all(db)
// .await
// .unwrap();
Ok(results)
}
22 changes: 22 additions & 0 deletions src/identity/types/user.rs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine

Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,28 @@ pub struct UserListParameters {
#[builder(default)]
#[validate(length(max = 64))]
pub unique_id: Option<String>,
/// Filter users by User Type (local, federated, nonlocal, all).
#[builder(default)]
#[serde(default, rename = "type")]
pub user_type: Option<UserType>,
}

/// 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.
Expand Down