Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
30 changes: 21 additions & 9 deletions rust/agama-lib/src/users/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ use serde::{Deserialize, Serialize};
#[serde(rename_all = "camelCase")]
pub struct UserSettings {
#[serde(rename = "user")]
#[serde(skip_serializing_if = "Option::is_none")]
pub first_user: Option<FirstUserSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub root: Option<RootUserSettings>,
}

Expand All @@ -39,12 +41,24 @@ pub struct UserSettings {
pub struct FirstUserSettings {
/// First user's full name
pub full_name: Option<String>,
/// First user password
#[serde(flatten)]
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<UserPassword>,
/// First user's username
pub user_name: Option<String>,
/// First user's password (in clear text)
pub password: Option<String>,
}

/// Represents a user password.
///
/// It holds the password and whether it is a hashed or a plain text password.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UserPassword {
/// User password
pub password: String,
/// Whether the password is hashed or is plain text
pub hashed_password: Option<bool>,
pub hashed_password: bool,
}

/// Root user settings
Expand All @@ -53,12 +67,10 @@ pub struct FirstUserSettings {
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RootUserSettings {
/// Root's password (in clear text)
#[serde(skip_serializing)]
pub password: Option<String>,
/// Whether the password is hashed or is plain text
#[serde(skip_serializing)]
pub hashed_password: Option<bool>,
/// Root user password
#[serde(flatten)]
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<UserPassword>,
/// Root SSH public key
#[serde(skip_serializing_if = "Option::is_none")]
pub ssh_public_key: Option<String>,
Expand Down
93 changes: 64 additions & 29 deletions rust/agama-lib/src/users/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
// find current contact information at www.suse.com.

use super::{
http_client::UsersHTTPClientError, FirstUser, FirstUserSettings, RootUserSettings,
UserSettings, UsersHTTPClient,
http_client::UsersHTTPClientError, settings::UserPassword, FirstUser, FirstUserSettings,
RootUserSettings, UserSettings, UsersHTTPClient,
};
use crate::http::BaseHTTPClient;

Expand Down Expand Up @@ -50,23 +50,44 @@ impl UsersStore {

pub async fn load(&self) -> UsersStoreResult<UserSettings> {
let first_user = self.users_client.first_user().await?;
let first_user = FirstUserSettings {
user_name: Some(first_user.user_name),
full_name: Some(first_user.full_name),
password: Some(first_user.password),
hashed_password: Some(first_user.hashed_password),
let first_user = if first_user.user_name.is_empty() {
None
} else {
let user_password = if first_user.password.is_empty() {
None
} else {
Some(UserPassword {
password: first_user.password,
hashed_password: first_user.hashed_password,
})
};

Some(FirstUserSettings {
user_name: Some(first_user.user_name),
full_name: Some(first_user.full_name),
Copy link
Contributor

Choose a reason for hiding this comment

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

this means we still export even empty full_name. Is it correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the full_name is not mandatory.

Copy link
Contributor

Choose a reason for hiding this comment

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

so in exported profile will be full_name: "" which looks wrong to me.

password: user_password,
})
};

let root_user = self.users_client.root_user().await?;
let root_user = RootUserSettings {
password: root_user.password,
hashed_password: root_user.hashed_password,
ssh_public_key: root_user.ssh_public_key,
let root_password = root_user
.password
.filter(|password| !password.is_empty())
.map(|password| UserPassword {
password,
hashed_password: root_user.hashed_password.unwrap_or_default(),
});
let ssh_public_key = root_user.ssh_public_key.filter(|key| !key.is_empty());
let root = if root_password.is_some() || ssh_public_key.is_some() {
Some(RootUserSettings {
password: root_password,
ssh_public_key,
})
} else {
None
};

Ok(UserSettings {
first_user: Some(first_user),
root: Some(root_user),
})
Ok(UserSettings { first_user, root })
}

pub async fn store(&self, settings: &UserSettings) -> UsersStoreResult<()> {
Expand All @@ -85,18 +106,24 @@ impl UsersStore {
let first_user = FirstUser {
user_name: settings.user_name.clone().unwrap_or_default(),
full_name: settings.full_name.clone().unwrap_or_default(),
password: settings.password.clone().unwrap_or_default(),
hashed_password: settings.hashed_password.unwrap_or_default(),
password: settings
.password
.as_ref()
.map(|p| p.password.clone())
.unwrap_or_default(),
hashed_password: settings
.password
.as_ref()
.map(|p| p.hashed_password)
.unwrap_or_default(),
Copy link
Contributor

Choose a reason for hiding this comment

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

when I see how tricky it is...maybe it belongs more to FirstUser or FirstUserSettings and having trait like Into?

Copy link
Contributor Author

@imobachgs imobachgs Jun 10, 2025

Choose a reason for hiding this comment

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

OK, I can change that. At first sight, I preferred to keep this logic just here because it is not needed anywhere else and, for consistency, I would have to implement other conversions too (From<&FirstUser> for FirstUserSettings, From<&RootUser> for RootUserSettings and From<&RootUserSettings> for RootUser).

};
Ok(self.users_client.set_first_user(&first_user).await?)
}

async fn store_root_user(&self, settings: &RootUserSettings) -> UsersStoreResult<()> {
let hashed_password = settings.hashed_password.unwrap_or_default();

if let Some(root_password) = &settings.password {
if let Some(password) = &settings.password {
self.users_client
.set_root_password(root_password, hashed_password)
.set_root_password(&password.password, password.hashed_password)
.await?;
}

Expand Down Expand Up @@ -160,13 +187,17 @@ mod test {
let first_user = FirstUserSettings {
full_name: Some("Tux".to_owned()),
user_name: Some("tux".to_owned()),
password: Some("fish".to_owned()),
hashed_password: Some(false),
password: Some(UserPassword {
password: "fish".to_owned(),
hashed_password: false,
}),
};
let root_user = RootUserSettings {
// FIXME this is weird: no matter what HTTP reports, we end up with None
password: Some("nots3cr3t".to_owned()),
hashed_password: Some(false),
password: Some(UserPassword {
password: "nots3cr3t".to_owned(),
hashed_password: false,
}),
ssh_public_key: Some("keykeykey".to_owned()),
};
let expected = UserSettings {
Expand Down Expand Up @@ -216,12 +247,16 @@ mod test {
let first_user = FirstUserSettings {
full_name: Some("Tux".to_owned()),
user_name: Some("tux".to_owned()),
password: Some("fish".to_owned()),
hashed_password: Some(false),
password: Some(UserPassword {
password: "fish".to_owned(),
hashed_password: false,
}),
};
let root_user = RootUserSettings {
password: Some("1234".to_owned()),
hashed_password: Some(false),
password: Some(UserPassword {
password: "1234".to_owned(),
hashed_password: false,
}),
ssh_public_key: Some("keykeykey".to_owned()),
};
let settings = UserSettings {
Expand Down
9 changes: 9 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
-------------------------------------------------------------------
Tue Jun 10 13:33:09 UTC 2025 - Imobach Gonzalez Sosa <[email protected]>

- Expose the user and the root password when exporting the configuration
(bsc#1235602).
- Do not export the "user" section unless a first user is defined.
- Do not export the "root" section unless an authentication mechanism
is defined.

-------------------------------------------------------------------
Fri Jun 6 13:22:58 UTC 2025 - Knut Anderssen <[email protected]>

Expand Down