Adding a new verb group remove_members (#434)

Co-authored-by: William Brown <william@blackhats.net.au>
This commit is contained in:
James Hodgkinson 2021-05-06 20:47:28 +10:00 committed by GitHub
parent 2bd8606cb6
commit e6f34d5dc5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 168 additions and 49 deletions

View file

@ -193,7 +193,7 @@ group
GET -> get this groups attr GET -> get this groups attr
PUT -> overwrite this group attr value list PUT -> overwrite this group attr value list
POST -> append this list to group attr POST -> append this list to group attr
DELETE -> purge this attr DELETE -> purge this attr (if body empty) or the elements listed in the body
schema schema
====== ======

View file

@ -129,7 +129,6 @@ impl KanidmAsyncClient {
// let dest = format!("{}{}", self.addr, dest); // let dest = format!("{}{}", self.addr, dest);
let req_string = serde_json::to_string(&request).map_err(ClientError::JSONEncode)?; let req_string = serde_json::to_string(&request).map_err(ClientError::JSONEncode)?;
let response = self let response = self
.client .client
.post(dest.as_str()) .post(dest.as_str())
@ -260,7 +259,55 @@ impl KanidmAsyncClient {
async fn perform_delete_request(&self, dest: &str) -> Result<bool, ClientError> { async fn perform_delete_request(&self, dest: &str) -> Result<bool, ClientError> {
let dest = format!("{}{}", self.addr, dest); let dest = format!("{}{}", self.addr, dest);
let response = self.client.delete(dest.as_str());
let response = self
.client
.delete(dest.as_str())
.header(CONTENT_TYPE, APPLICATION_JSON);
let response = if let Some(token) = &self.bearer_token {
response.bearer_auth(token)
} else {
response
};
let response = response.send().await.map_err(ClientError::Transport)?;
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => {
return Err(ClientError::Http(
unexpect,
response.json().await.ok(),
opid,
))
}
}
response
.json()
.await
.map_err(|e| ClientError::JSONDecode(e, opid))
}
async fn perform_delete_request_with_body<R: Serialize>(
&self,
dest: &str,
request: R,
) -> Result<bool, ClientError> {
let dest = format!("{}{}", self.addr, dest);
let req_string = serde_json::to_string(&request).map_err(ClientError::JSONEncode)?;
let response = self
.client
.delete(dest.as_str())
.body(req_string)
.header(CONTENT_TYPE, APPLICATION_JSON);
let response = if let Some(token) = &self.bearer_token { let response = if let Some(token) = &self.bearer_token {
response.bearer_auth(token) response.bearer_auth(token)
} else { } else {
@ -658,11 +705,28 @@ impl KanidmAsyncClient {
.await .await
} }
/* pub async fn idm_group_remove_members(
pub fn idm_group_remove_member(&self, id: &str, member: &str) -> Result<(), ClientError> { &self,
unimplemented!(); group: &str,
members: &[&str],
) -> Result<bool, ClientError> {
debug!(
"{}",
[
"Asked to remove members ",
&members.join(","),
" from ",
group
]
.concat()
.to_string()
);
self.perform_delete_request_with_body(
["/v1/group/", group, "/_attr/member"].concat().as_str(),
&members,
)
.await
} }
*/
pub async fn idm_group_purge_members(&self, id: &str) -> Result<bool, ClientError> { pub async fn idm_group_purge_members(&self, id: &str) -> Result<bool, ClientError> {
self.perform_delete_request(format!("/v1/group/{}/_attr/member", id).as_str()) self.perform_delete_request(format!("/v1/group/{}/_attr/member", id).as_str())

View file

@ -457,11 +457,13 @@ impl KanidmClient {
tokio_block_on(self.asclient.idm_group_add_members(id, members)) tokio_block_on(self.asclient.idm_group_add_members(id, members))
} }
/* pub fn idm_group_remove_members(
pub fn idm_group_remove_member(&self, id: &str, member: &str) -> Result<(), ClientError> { &self,
unimplemented!(); group: &str,
members: &[&str],
) -> Result<bool, ClientError> {
tokio_block_on(self.asclient.idm_group_remove_members(group, members))
} }
*/
pub fn idm_group_purge_members(&self, id: &str) -> Result<bool, ClientError> { pub fn idm_group_purge_members(&self, id: &str) -> Result<bool, ClientError> {
tokio_block_on(self.asclient.idm_group_purge_members(id)) tokio_block_on(self.asclient.idm_group_purge_members(id))

View file

@ -286,13 +286,11 @@ fn test_server_rest_group_lifecycle() {
); );
// Remove a member from the group // Remove a member from the group
/*
rsclient rsclient
.idm_group_remove_member("demo_group", "demo_group") .idm_group_remove_members("demo_group", &["demo_group"])
.unwrap(); .unwrap();
let members = rsclient.idm_group_get_members("demo_group").unwrap(); let members = rsclient.idm_group_get_members("demo_group").unwrap();
assert!(members == vec!["admin".to_string()]); assert!(members == Some(vec!["admin@example.com".to_string()]));
*/
// purge members // purge members
rsclient.idm_group_purge_members("demo_group").unwrap(); rsclient.idm_group_purge_members("demo_group").unwrap();

View file

@ -9,6 +9,7 @@ impl GroupOpt {
GroupOpt::Delete(gcopt) => gcopt.copt.debug, GroupOpt::Delete(gcopt) => gcopt.copt.debug,
GroupOpt::ListMembers(gcopt) => gcopt.copt.debug, GroupOpt::ListMembers(gcopt) => gcopt.copt.debug,
GroupOpt::AddMembers(gcopt) => gcopt.copt.debug, GroupOpt::AddMembers(gcopt) => gcopt.copt.debug,
GroupOpt::RemoveMembers(gcopt) => gcopt.copt.debug,
GroupOpt::SetMembers(gcopt) => gcopt.copt.debug, GroupOpt::SetMembers(gcopt) => gcopt.copt.debug,
GroupOpt::PurgeMembers(gcopt) => gcopt.copt.debug, GroupOpt::PurgeMembers(gcopt) => gcopt.copt.debug,
GroupOpt::Posix(gpopt) => match gpopt { GroupOpt::Posix(gpopt) => match gpopt {
@ -74,6 +75,18 @@ impl GroupOpt {
eprintln!("Error -> {:?}", e); eprintln!("Error -> {:?}", e);
} }
} }
GroupOpt::RemoveMembers(gcopt) => {
let client = gcopt.copt.to_client();
let remove_members: Vec<&str> = gcopt.members.iter().map(|s| s.as_str()).collect();
if let Err(e) =
client.idm_group_remove_members(gcopt.name.as_str(), &remove_members)
{
eprintln!("Failed to remove members -> {:?}", e);
}
}
GroupOpt::SetMembers(gcopt) => { GroupOpt::SetMembers(gcopt) => {
let client = gcopt.copt.to_client(); let client = gcopt.copt.to_client();
let new_members: Vec<&str> = gcopt.members.iter().map(|s| s.as_str()).collect(); let new_members: Vec<&str> = gcopt.members.iter().map(|s| s.as_str()).collect();

View file

@ -64,6 +64,8 @@ pub enum GroupOpt {
PurgeMembers(Named), PurgeMembers(Named),
#[structopt(name = "add_members")] #[structopt(name = "add_members")]
AddMembers(GroupNamedMembers), AddMembers(GroupNamedMembers),
#[structopt(name = "remove_members")]
RemoveMembers(GroupNamedMembers),
#[structopt(name = "posix")] #[structopt(name = "posix")]
Posix(GroupPosix), Posix(GroupPosix),
} }

View file

@ -211,12 +211,12 @@ pub struct PurgeAttributeMessage {
pub eventid: Uuid, pub eventid: Uuid,
} }
/// Delete a single attribute-value pair from the entry. /// Delete a set of attribute-value pair from the entry.
pub struct RemoveAttributeValueMessage { pub struct RemoveAttributeValuesMessage {
pub uat: Option<UserAuthToken>, pub uat: Option<UserAuthToken>,
pub uuid_or_name: String, pub uuid_or_name: String,
pub attr: String, pub attr: String,
pub value: String, pub values: Vec<String>,
pub filter: Filter<FilterInvalid>, pub filter: Filter<FilterInvalid>,
pub eventid: Uuid, pub eventid: Uuid,
} }
@ -886,33 +886,46 @@ impl QueryServerWriteV1 {
res res
} }
pub async fn handle_removeattributevalue( pub async fn handle_removeattributevalues(
&self, &self,
msg: RemoveAttributeValueMessage, msg: RemoveAttributeValuesMessage,
) -> Result<(), OperationError> { ) -> Result<(), OperationError> {
let mut audit = AuditScope::new("remove_attribute_value", msg.eventid, self.log_level); let RemoveAttributeValuesMessage {
uat,
uuid_or_name,
attr,
values,
filter,
eventid,
} = msg;
let mut audit = AuditScope::new("remove_attribute_values", eventid, self.log_level);
let idms_prox_write = self.idms.proxy_write_async(duration_from_epoch_now()).await; let idms_prox_write = self.idms.proxy_write_async(duration_from_epoch_now()).await;
let res = lperf_op_segment!( let res = lperf_op_segment!(
&mut audit, &mut audit,
"actors::v1_write::handle<RemoveAttributeValueMessage>", "actors::v1_write::handle<RemoveAttributeValuesMessage>",
|| { || {
let target_uuid = idms_prox_write let target_uuid = idms_prox_write
.qs_write .qs_write
.name_to_uuid(&mut audit, msg.uuid_or_name.as_str()) .name_to_uuid(&mut audit, uuid_or_name.as_str())
.map_err(|e| { .map_err(|e| {
ladmin_error!(audit, "Error resolving id to target"); ladmin_error!(audit, "Error resolving id to target");
e e
})?; })?;
let proto_ml = let proto_ml = ProtoModifyList::new_list(
ProtoModifyList::new_list(vec![ProtoModify::Removed(msg.attr, msg.value)]); values
.into_iter()
.map(|v| ProtoModify::Removed(attr.clone(), v))
.collect(),
);
let mdf = match ModifyEvent::from_parts( let mdf = match ModifyEvent::from_parts(
&mut audit, &mut audit,
msg.uat.as_ref(), uat.as_ref(),
target_uuid, target_uuid,
&proto_ml, &proto_ml,
msg.filter, filter,
&idms_prox_write.qs_write, &idms_prox_write.qs_write,
) { ) {
Ok(m) => m, Ok(m) => m,

View file

@ -11,7 +11,8 @@ use crate::actors::v1_write::{
IdmAccountSetPasswordMessage, IdmAccountUnixExtendMessage, IdmAccountUnixSetCredMessage, IdmAccountSetPasswordMessage, IdmAccountUnixExtendMessage, IdmAccountUnixSetCredMessage,
IdmGroupUnixExtendMessage, InternalCredentialSetMessage, InternalDeleteMessage, IdmGroupUnixExtendMessage, InternalCredentialSetMessage, InternalDeleteMessage,
InternalRegenerateRadiusMessage, InternalSshKeyCreateMessage, ModifyMessage, InternalRegenerateRadiusMessage, InternalSshKeyCreateMessage, ModifyMessage,
PurgeAttributeMessage, RemoveAttributeValueMessage, ReviveRecycledMessage, SetAttributeMessage, PurgeAttributeMessage, RemoveAttributeValuesMessage, ReviveRecycledMessage,
SetAttributeMessage,
}; };
use crate::config::TlsConfiguration; use crate::config::TlsConfiguration;
use crate::event::AuthResult; use crate::event::AuthResult;
@ -360,7 +361,6 @@ async fn json_rest_event_post_id_attr(
let id = req.get_url_param("id")?; let id = req.get_url_param("id")?;
let attr = req.get_url_param("attr")?; let attr = req.get_url_param("attr")?;
let values: Vec<String> = req.body_json().await?; let values: Vec<String> = req.body_json().await?;
let (eventid, hvalue) = new_eventid!(); let (eventid, hvalue) = new_eventid!();
let m_obj = AppendAttributeMessage { let m_obj = AppendAttributeMessage {
uat, uat,
@ -408,17 +408,26 @@ async fn json_rest_event_put_id_attr(
} }
async fn json_rest_event_delete_id_attr( async fn json_rest_event_delete_id_attr(
req: tide::Request<AppState>, mut req: tide::Request<AppState>,
filter: Filter<FilterInvalid>, filter: Filter<FilterInvalid>,
// Seperate for account_delete_id_radius // Seperate for account_delete_id_radius
attr: String, attr: String,
) -> tide::Result { ) -> tide::Result {
let uat = req.get_current_uat(); let uat = req.get_current_uat();
let id = req.get_url_param("id")?; let id = req.get_url_param("id")?;
let (eventid, hvalue) = new_eventid!(); let (eventid, hvalue) = new_eventid!();
// TODO #211: Attempt to get an option Vec<String> here? // TODO #211: Attempt to get an option Vec<String> here?
// It's probably better to focus on SCIM instead, it seems richer than this. // It's probably better to focus on SCIM instead, it seems richer than this.
let body = req.take_body();
let values: Vec<String> = if body.is_empty().unwrap_or(true) {
vec![]
} else {
// Must now be a valid list.
body.into_json().await?
};
if values.len() == 0 {
let m_obj = PurgeAttributeMessage { let m_obj = PurgeAttributeMessage {
uat, uat,
uuid_or_name: id, uuid_or_name: id,
@ -433,6 +442,24 @@ async fn json_rest_event_delete_id_attr(
.await .await
.map(|()| true); .map(|()| true);
to_tide_response(res, hvalue) to_tide_response(res, hvalue)
} else {
let obj = RemoveAttributeValuesMessage {
uat,
uuid_or_name: id,
attr,
values,
filter,
eventid,
};
let res = req
.state()
.qe_w_ref
.handle_removeattributevalues(obj)
.await
.map(|()| true);
to_tide_response(res, hvalue)
}
} }
async fn json_rest_event_credential_put( async fn json_rest_event_credential_put(
@ -684,11 +711,11 @@ pub async fn account_delete_id_ssh_pubkey_tag(req: tide::Request<AppState>) -> t
let tag = req.get_url_param("tag")?; let tag = req.get_url_param("tag")?;
let (eventid, hvalue) = new_eventid!(); let (eventid, hvalue) = new_eventid!();
let obj = RemoveAttributeValueMessage { let obj = RemoveAttributeValuesMessage {
uat, uat,
uuid_or_name: id, uuid_or_name: id,
attr: "ssh_publickey".to_string(), attr: "ssh_publickey".to_string(),
value: tag, values: vec![tag],
filter: filter_all!(f_eq("class", PartialValue::new_class("account"))), filter: filter_all!(f_eq("class", PartialValue::new_class("account"))),
eventid, eventid,
}; };
@ -696,7 +723,7 @@ pub async fn account_delete_id_ssh_pubkey_tag(req: tide::Request<AppState>) -> t
let res = req let res = req
.state() .state()
.qe_w_ref .qe_w_ref
.handle_removeattributevalue(obj) .handle_removeattributevalues(obj)
.await .await
.map(|()| true); .map(|()| true);
to_tide_response(res, hvalue) to_tide_response(res, hvalue)
@ -1449,10 +1476,10 @@ pub fn create_https_server(
.delete(group_id_delete); .delete(group_id_delete);
group_route group_route
.at("/:id/_attr/:attr") .at("/:id/_attr/:attr")
.delete(group_id_delete_attr)
.get(group_id_get_attr) .get(group_id_get_attr)
.put(group_id_put_attr) .put(group_id_put_attr)
.post(group_id_post_attr) .post(group_id_post_attr);
.delete(group_id_delete_attr);
group_route.at("/:id/_unix").post(group_post_id_unix); group_route.at("/:id/_unix").post(group_post_id_unix);
group_route group_route
.at("/:id/_unix/_token") .at("/:id/_unix/_token")