This commit is contained in:
Keerthi 2025-04-24 09:39:24 +00:00 committed by GitHub
commit 7ca02cde00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 111 additions and 9 deletions
libs/client/src
tools/cli/src

View file

@ -6,6 +6,10 @@ use kanidm_proto::v1::{AccountUnixExtend, Entry, SingleStringRequest, UatStatus}
use uuid::Uuid;
use crate::{ClientError, KanidmClient};
use kanidm_proto::scim_v1::PatchRequest;
use serde_json::Value;
use tracing::trace;
impl KanidmClient {
pub async fn idm_person_account_list(&self) -> Result<Vec<Entry>, ClientError> {
@ -286,4 +290,16 @@ impl KanidmClient {
self.perform_post_request(format!("/v1/person/{}/_certificate", id).as_str(), new_cert)
.await
}
pub async fn idm_person_account_patch(
&self,
id: &str,
patch_request: PatchRequest,
) -> Result<(), ClientError> {
if patch_request.operations.is_empty() {
return Ok(());
}
self.perform_patch_request_scim(format!("/v1/person/{}", id).as_str(), patch_request)
.await
}
}

View file

@ -30,6 +30,36 @@ use crate::{
AccountSsh, AccountUserAuthToken, AccountValidity, OutputMode, PersonOpt, PersonPosix,
};
use kanidm_client::ClientError;
use kanidm_client::OutputFormat;
use kanidm_client::SSHKEY_ATTEST_FEATURE;
use kanidm_proto::identity::v1::{
IdentityGetReply, KeyAttestationVerifyRequest, KeyType as SSHKeyAttestationType,
};
use kanidm_proto::identity::{self};
use kanidm_proto::internal::{
CUCredState, CURegState, CURegWarning, CURequest, CUSessionToken, CUStatus, CredentialDetail,
PasskeyDetail, SshPublicKey, TotpSecret,
};
use kanidm_proto::scim_v1::{
client::{ScimSshPublicKeys, PATCHOP_SCHEMA_URI},
PatchOp, PatchOperation, PatchRequest, ScimEntryGetQuery,
ATTR_DISPLAYNAME, ATTR_GIDNUMBER, ATTR_LEGALNAME, ATTR_MAIL, ATTR_NAME, ATTR_PASSWORD, ATTR_POSIXACCOUNTS,
ATTR_SSH_PUBLICKEY,
};
use kanidm_proto::MessageError;
use serde_json::json;
use sshkey_attest::{Error as AttestationError, RegisterChallengeResponse};
use sshkeys::PublicKey as ActualSshPublicKey;
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;
use dialoguer::Input;
use tracing::{debug, error, info, trace, warn};
use uuid::Uuid;
impl PersonOpt {
pub fn debug(&self) -> bool {
match self {
@ -321,14 +351,71 @@ impl PersonOpt {
}
PersonOpt::Update(aopt) => {
let client = aopt.copt.to_client(OpType::Write).await;
let mut patch_ops = Vec::new();
// Helper to create a replace operation
let replace_op = |path: &str, value: &str| -> PatchOperation {
PatchOperation {
op: PatchOp::Replace,
path: Some(path.to_string()),
value: Some(json!(value)),
}
};
// Helper to create a replace operation for Vec<String>
let replace_op_vec = |path: &str, value: &[String]| -> PatchOperation {
PatchOperation {
op: PatchOp::Replace,
path: Some(path.to_string()),
value: Some(json!(value)),
}
};
// Helper to create a remove operation
let remove_op = |path: &str| -> PatchOperation {
PatchOperation {
op: PatchOp::Remove,
path: Some(path.to_string()),
value: None,
}
};
if let Some(newname) = &aopt.newname {
patch_ops.push(replace_op(ATTR_NAME, newname));
}
if let Some(displayname) = &aopt.displayname {
patch_ops.push(replace_op(ATTR_DISPLAYNAME, displayname));
}
if let Some(legalname) = &aopt.legalname {
if legalname.is_empty() {
// Empty string means clear the attribute
patch_ops.push(remove_op(ATTR_LEGALNAME));
} else {
patch_ops.push(replace_op(ATTR_LEGALNAME, legalname));
}
}
if let Some(mail) = &aopt.mail {
if mail.is_empty() {
// Empty list means clear the attribute
patch_ops.push(remove_op(ATTR_MAIL));
} else {
patch_ops.push(replace_op_vec(ATTR_MAIL, mail));
}
}
if patch_ops.is_empty() {
println!("No changes specified.");
return;
}
let patch_request = PatchRequest {
schemas: vec![PATCHOP_SCHEMA_URI.to_string()],
operations: patch_ops,
};
match client
.idm_person_account_update(
aopt.aopts.account_id.as_str(),
aopt.newname.as_deref(),
aopt.displayname.as_deref(),
aopt.legalname.as_deref(),
aopt.mail.as_deref(),
)
.idm_person_account_patch(aopt.aopts.account_id.as_str(), patch_request)
.await
{
Ok(()) => println!("Success"),

View file

@ -594,8 +594,7 @@ pub enum ServiceAccountPosix {
pub struct PersonUpdateOpt {
#[clap(flatten)]
aopts: AccountCommonOpt,
#[clap(long, short, help = "Set the legal name for the person.",
value_parser = clap::builder::NonEmptyStringValueParser::new())]
#[clap(long, short, help = "Set the legal name for the person. Use '' to clear.")]
legalname: Option<String>,
#[clap(long, short, help = "Set the account name for the person.",
value_parser = clap::builder::NonEmptyStringValueParser::new())]