use crate::plugins::Plugin;
use crate::audit::AuditScope;
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew};
use crate::event::{CreateEvent, ModifyEvent};
use crate::server::QueryServerWriteTransaction;
use crate::utils::uuid_to_gid_u32;
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::OperationError;
const GID_SYSTEM_NUMBER_MIN: u32 = 65536;
const GID_SAFETY_NUMBER_MIN: u32 = 1000;
lazy_static! {
static ref CLASS_POSIXGROUP: PartialValue = PartialValue::new_class("posixgroup");
static ref CLASS_POSIXACCOUNT: PartialValue = PartialValue::new_class("posixaccount");
}
pub struct GidNumber {}
fn apply_gidnumber<T: Clone>(
au: &mut AuditScope,
e: &mut Entry<EntryInvalid, T>,
) -> Result<(), OperationError> {
if (e.attribute_value_pres("class", &CLASS_POSIXGROUP)
|| e.attribute_value_pres("class", &CLASS_POSIXACCOUNT))
&& !e.attribute_pres("gidnumber")
{
let u_ref = e
.get_uuid()
.ok_or(OperationError::InvalidEntryState)
.map_err(|e| {
ladmin_error!(au, "Invalid Entry State - Missing UUID");
e
})?;
let gid = uuid_to_gid_u32(u_ref);
if gid < GID_SYSTEM_NUMBER_MIN {
return Err(OperationError::InvalidAttribute(format!(
"gidnumber {} may overlap with system range {}",
gid, GID_SYSTEM_NUMBER_MIN
)));
}
let gid_v = Value::new_uint32(gid);
ladmin_info!(au, "Generated {} for {:?}", gid, u_ref);
e.set_ava("gidnumber", btreeset![gid_v]);
Ok(())
} else if let Some(gid) = e.get_ava_single_uint32("gidnumber") {
if gid <= GID_SAFETY_NUMBER_MIN {
Err(OperationError::InvalidAttribute(format!(
"gidnumber {} overlaps into system secure range {}",
gid, GID_SAFETY_NUMBER_MIN
)))
} else {
Ok(())
}
} else {
Ok(())
}
}
impl Plugin for GidNumber {
fn id() -> &'static str {
"plugin_gidnumber"
}
fn pre_create_transform(
au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
_ce: &CreateEvent,
) -> Result<(), OperationError> {
for e in cand.iter_mut() {
apply_gidnumber(au, e)?;
}
Ok(())
}
fn pre_modify(
au: &mut AuditScope,
_qs: &QueryServerWriteTransaction,
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
_me: &ModifyEvent,
) -> Result<(), OperationError> {
for e in cand.iter_mut() {
apply_gidnumber(au, e)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::audit::AuditScope;
use crate::entry::{Entry, EntryInit, EntryNew};
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::OperationError;
use uuid::Uuid;
fn check_gid(
au: &mut AuditScope,
qs_write: &QueryServerWriteTransaction,
uuid: &str,
gid: u32,
) {
let u = Uuid::parse_str(uuid).unwrap();
let e = qs_write.internal_search_uuid(au, &u).unwrap();
let gidnumber = e.get_ava_single("gidnumber").unwrap();
let ex_gid = Value::new_uint32(gid);
assert!(&ex_gid == gidnumber);
}
#[test]
fn test_gidnumber_create_generate() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Ok(()),
preload,
create,
None,
|au, qs_write: &QueryServerWriteTransaction| check_gid(
au,
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
0x997ef244
)
);
}
#[test]
fn test_gidnumber_create_noaction() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"gidnumber": ["10001"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Ok(()),
preload,
create,
None,
|au, qs_write: &QueryServerWriteTransaction| check_gid(
au,
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
10001
)
);
}
#[test]
fn test_gidnumber_modify_generate() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let preload = vec![e];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iname("testperson"))),
modlist!([m_pres("class", &Value::new_class("posixgroup"))]),
None,
|au, qs_write: &QueryServerWriteTransaction| check_gid(
au,
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
0x997ef244
)
);
}
#[test]
fn test_gidnumber_modify_regenerate() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"gidnumber": ["2000"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let preload = vec![e];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iname("testperson"))),
modlist!([m_purge("gidnumber")]),
None,
|au, qs_write: &QueryServerWriteTransaction| check_gid(
au,
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
0x997ef244
)
);
}
#[test]
fn test_gidnumber_modify_noregen() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"gidnumber": ["3999"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let preload = vec![e];
run_modify_test!(
Ok(()),
preload,
filter!(f_eq("name", PartialValue::new_iname("testperson"))),
modlist!([
m_purge("gidnumber"),
m_pres("gidnumber", &Value::new_uint32(2000))
]),
None,
|au, qs_write: &QueryServerWriteTransaction| check_gid(
au,
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
2000
)
);
}
#[test]
fn test_gidnumber_create_system_reject() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b00000244"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Err(OperationError::InvalidAttribute(
"gidnumber 580 may overlap with system range 65536".to_string()
)),
preload,
create,
None,
|_, _| {}
);
}
#[test]
fn test_gidnumber_create_secure_reject() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"gidnumber": ["500"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Err(OperationError::InvalidAttribute(
"gidnumber 500 overlaps into system secure range 1000".to_string()
)),
preload,
create,
None,
|_, _| {}
);
}
#[test]
fn test_gidnumber_create_secure_root_reject() {
let e: Entry<EntryInit, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"gidnumber": ["0"],
"description": ["testperson"],
"displayname": ["testperson"]
}
}"#,
);
let create = vec![e.clone()];
let preload = Vec::new();
run_create_test!(
Err(OperationError::InvalidAttribute(
"gidnumber 0 overlaps into system secure range 1000".to_string()
)),
preload,
create,
None,
|_, _| {}
);
}
}