CLI: Allow editing principal password

This commit is contained in:
Lennart
2025-04-26 10:52:23 +02:00
parent 6b9bd1226f
commit 4071ea4ff4
6 changed files with 68 additions and 22 deletions

View File

@@ -281,14 +281,17 @@ impl<AP: AuthenticationProvider> UserStore for OidcUserStore<AP> {
async fn insert_user(&self, id: &str) -> Result<(), Self::Error> { async fn insert_user(&self, id: &str) -> Result<(), Self::Error> {
self.0 self.0
.insert_principal(User { .insert_principal(
id: id.to_owned(), User {
displayname: None, id: id.to_owned(),
principal_type: Default::default(), displayname: None,
password: None, principal_type: Default::default(),
app_tokens: vec![], password: None,
memberships: vec![], app_tokens: vec![],
}) memberships: vec![],
},
false,
)
.await .await
} }
} }

View File

@@ -9,7 +9,7 @@ pub trait AuthenticationProvider: 'static {
async fn get_principals(&self) -> Result<Vec<User>, crate::Error>; async fn get_principals(&self) -> Result<Vec<User>, crate::Error>;
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>; async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>;
async fn remove_principal(&self, id: &str) -> Result<(), crate::Error>; async fn remove_principal(&self, id: &str) -> Result<(), crate::Error>;
async fn insert_principal(&self, user: User) -> Result<(), crate::Error>; async fn insert_principal(&self, user: User, overwrite: bool) -> Result<(), crate::Error>;
async fn validate_password(&self, user_id: &str, password: &str) async fn validate_password(&self, user_id: &str, password: &str)
-> Result<Option<User>, Error>; -> Result<Option<User>, Error>;
async fn validate_app_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>; async fn validate_app_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;

View File

@@ -69,9 +69,9 @@ impl AuthenticationProvider for TomlPrincipalStore {
Ok(self.principals.read().await.get(id).cloned()) Ok(self.principals.read().await.get(id).cloned())
} }
async fn insert_principal(&self, user: User) -> Result<(), crate::Error> { async fn insert_principal(&self, user: User, overwrite: bool) -> Result<(), crate::Error> {
let mut principals = self.principals.write().await; let mut principals = self.principals.write().await;
if principals.contains_key(&user.id) { if !overwrite && principals.contains_key(&user.id) {
return Err(Error::AlreadyExists); return Err(Error::AlreadyExists);
} }
principals.insert(user.id.clone(), user); principals.insert(user.id.clone(), user);

View File

@@ -31,7 +31,7 @@ pub fn cmd_gen_config(_args: GenConfigArgs) -> anyhow::Result<()> {
path: "/etc/rustical/principals.toml".to_owned(), path: "/etc/rustical/principals.toml".to_owned(),
}), }),
data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig { data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig {
db_url: "".to_owned(), db_url: "/var/lib/rustical/db.sqlite3".to_owned(),
}), }),
tracing: TracingConfig::default(), tracing: TracingConfig::default(),
frontend: FrontendConfig { frontend: FrontendConfig {

View File

@@ -35,16 +35,28 @@ struct RemoveArgs {
id: String, id: String,
} }
#[derive(Parser, Debug)]
struct EditArgs {
id: String,
#[arg(long, help = "Ask for password input")]
password: bool,
#[arg(
long,
help = "Remove password (If you only want to use OIDC for example)"
)]
remove_password: bool,
}
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
enum Command { enum Command {
List, List,
Create(CreateArgs), Create(CreateArgs),
Remove(RemoveArgs), Remove(RemoveArgs),
Edit(EditArgs),
} }
pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> { pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> {
let config: Config = Figment::new() let config: Config = Figment::new()
// TODO: What to do when config file does not exist?
.merge(Toml::file(&args.config_file)) .merge(Toml::file(&args.config_file))
.merge(Env::prefixed("RUSTICAL_").split("__")) .merge(Env::prefixed("RUSTICAL_").split("__"))
.extract()?; .extract()?;
@@ -85,14 +97,17 @@ pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> {
None None
}; };
user_store user_store
.insert_principal(User { .insert_principal(
id, User {
displayname: name, id,
principal_type: principal_type.unwrap_or_default(), displayname: name,
app_tokens: vec![], principal_type: principal_type.unwrap_or_default(),
password, app_tokens: vec![],
memberships: vec![], password,
}) memberships: vec![],
},
false,
)
.await?; .await?;
println!("Principal created"); println!("Principal created");
} }
@@ -100,6 +115,34 @@ pub async fn cmd_principals(args: PrincipalsArgs) -> anyhow::Result<()> {
user_store.remove_principal(&id).await?; user_store.remove_principal(&id).await?;
println!("Principal {id} removed"); println!("Principal {id} removed");
} }
Command::Edit(EditArgs {
id,
remove_password,
password,
}) => {
let mut principal = user_store
.get_principal(&id)
.await?
.unwrap_or_else(|| panic!("Principal {id} does not exist"));
if remove_password {
principal.password = None;
}
if password {
let salt = SaltString::generate(OsRng);
println!("Enter your password:");
let password = rpassword::read_password()?;
principal.password = Some(
argon2::Argon2::default()
.hash_password(password.as_bytes(), &salt)
.unwrap()
.to_string()
.into(),
)
}
user_store.insert_principal(principal, true).await?;
println!("Principal {id} updated");
}
} }
Ok(()) Ok(())
} }

View File

@@ -77,7 +77,6 @@ async fn main() -> Result<()> {
Some(Command::Principals(principals_args)) => cmd_principals(principals_args).await?, Some(Command::Principals(principals_args)) => cmd_principals(principals_args).await?,
None => { None => {
let config: Config = Figment::new() let config: Config = Figment::new()
// TODO: What to do when config file does not exist?
.merge(Toml::file(&args.config_file)) .merge(Toml::file(&args.config_file))
.merge(Env::prefixed("RUSTICAL_").split("__")) .merge(Env::prefixed("RUSTICAL_").split("__"))
.extract()?; .extract()?;
@@ -196,6 +195,7 @@ mod tests {
async fn insert_principal( async fn insert_principal(
&self, &self,
_user: rustical_store::auth::User, _user: rustical_store::auth::User,
_overwrite: bool,
) -> Result<(), rustical_store::Error> { ) -> Result<(), rustical_store::Error> {
Err(rustical_store::Error::Other(anyhow!("Not implemented"))) Err(rustical_store::Error::Other(anyhow!("Not implemented")))
} }