23 gidnumber generation ()

Implements  gidnumber generation. This automatically creates gid numbers for posixaccounts and posixgroups based on the UUID of the object. Alternately, these can be provided if manual allocation is desired. This is an important step in posix attribute support.
This commit is contained in:
Firstyear 2019-11-30 09:39:31 +10:00 committed by GitHub
parent 6faf79db03
commit 000a24b49e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 522 additions and 8 deletions

View file

@ -2,5 +2,33 @@
gid number generation
---------------------
TBD
Gid number generation helps to ease admin burden for posix accounts by dynamically allocating
the gidnumbers on accounts in a way that is distributed and safe for a multi-write server
environment.
Allocation Algorithm
--------------------
As each entry has a UUID which is a 128 bit random identifier, we can use this for our gid number
by extracting the last 32 bits.
Why only gid number?
--------------------
It's a common misconception that uid is the only seperation on linux that matters. When a user
account exists, it has a primary user id AND a primary group id. Default umask grants rw to any
member of the same primary group id, which leads to misconfigurations where an admin in the intent
of saying "all users belong to default_users" ends up granting all users the right to read and write
all other users folders.
Additionally, there are rights around process and ptrace that exist for the same gid as well.
In this way, uid and primary gid of a user MUST be unique to the user, and many systems (like
SSSD's dynamic gid allocation from AD and FreeIPA) make effort to assign a user-private-group
to combat this issue.
Instead of creating a group per account, we instead *imply* that the gidnumber *is* the uidnumber,
and that a posixaccount *implies* the existance of a user private group that the pam/nsswitch
tools will generate on the client. This also guarantees that posixgroups will never conflict or
overlap with the uid namespace with weth attr uniqueness plugin.

View file

@ -38,4 +38,5 @@ pub enum DbValueV1 {
RU(String),
SK(DbValueTaggedStringV1),
SP(String, String),
UI(u32),
}

View file

@ -105,6 +105,10 @@ pub static UUID_SCHEMA_ATTR_DOMAIN_NAME: &'static str = "00000000-0000-0000-0000
pub static UUID_SCHEMA_ATTR_DOMAIN_UUID: &'static str = "00000000-0000-0000-0000-ffff00000054";
pub static UUID_SCHEMA_ATTR_DOMAIN_SSID: &'static str = "00000000-0000-0000-0000-ffff00000055";
pub static UUID_SCHEMA_ATTR_GIDNUMBER: &'static str = "00000000-0000-0000-0000-ffff00000056";
pub static UUID_SCHEMA_CLASS_POSIXACCOUNT: &'static str = "00000000-0000-0000-0000-ffff00000057";
pub static UUID_SCHEMA_CLASS_POSIXGROUP: &'static str = "00000000-0000-0000-0000-ffff00000058";
// System and domain infos
// I'd like to strongly criticise william of the past for fucking up these allocations.
pub static _UUID_SYSTEM_INFO: &'static str = "00000000-0000-0000-0000-ffffff000001";
@ -602,6 +606,7 @@ pub static JSON_IDM_ALL_ACP_READ_V1: &'static str = r#"{
"memberof",
"member",
"uuid",
"gidnumber",
"ssh_publickey"
]
}
@ -701,7 +706,7 @@ pub static JSON_IDM_ACP_ACCOUNT_READ_PRIV_V1: &'static str = r#"{
"{\"And\": [{\"Eq\": [\"class\",\"account\"]}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}"
],
"acp_search_attr": [
"class", "name", "spn", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof", "mail"
"class", "name", "spn", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof", "mail", "gidnumber"
]
}
}"#;
@ -754,10 +759,11 @@ pub static JSON_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{
"displayname",
"description",
"primary_credential",
"ssh_publickey"
"ssh_publickey",
"gidnumber"
],
"acp_create_class": [
"object", "account"
"object", "account", "posixaccount"
]
}
}"#;
@ -1128,10 +1134,11 @@ pub static JSON_IDM_ACP_GROUP_MANAGE_PRIV_V1: &'static str = r#"{
"class",
"name",
"description",
"gidnumber",
"member"
],
"acp_create_class": [
"object", "group"
"object", "group", "posixgroup"
]
}
}"#;
@ -1532,6 +1539,34 @@ pub static JSON_SCHEMA_ATTR_DOMAIN_SSID: &'static str = r#"{
]
}
}"#;
pub static JSON_SCHEMA_ATTR_GIDNUMBER: &'static str = r#"{
"attrs": {
"class": [
"object",
"system",
"attributetype"
],
"description": [
"The groupid (uid) number of a group or account. This is the same value as the UID number on posix accounts for security reasons."
],
"index": [],
"unique": [
"true"
],
"multivalue": [
"false"
],
"attributename": [
"gidnumber"
],
"syntax": [
"UINT32"
],
"uuid": [
"00000000-0000-0000-0000-ffff00000056"
]
}
}"#;
pub static JSON_SCHEMA_CLASS_PERSON: &'static str = r#"
{
@ -1662,6 +1697,53 @@ pub static JSON_SCHEMA_CLASS_DOMAIN_INFO: &'static str = r#"
}
"#;
pub static JSON_SCHEMA_CLASS_POSIXGROUP: &'static str = r#"
{
"attrs": {
"class": [
"object",
"system",
"classtype"
],
"description": [
"Object representation of a posix group, requires group"
],
"classname": [
"posixgroup"
],
"systemmust": [
"gidnumber"
],
"uuid": [
"00000000-0000-0000-0000-ffff00000058"
]
}
}
"#;
pub static JSON_SCHEMA_CLASS_POSIXACCOUNT: &'static str = r#"
{
"attrs": {
"class": [
"object",
"system",
"classtype"
],
"description": [
"Object representation of a posix account, requires account"
],
"classname": [
"posixaccount"
],
"systemmust": [
"gidnumber"
],
"uuid": [
"00000000-0000-0000-0000-ffff00000057"
]
}
}
"#;
// need a domain_trust_info as well.
// TODO

View file

@ -329,6 +329,15 @@ impl Entry<EntryInvalid, EntryNew> {
})
}).collect()
}
"gidnumber" => {
vs.into_iter().map(|v| {
Value::new_uint32_str(v.as_str())
.unwrap_or_else(|| {
warn!("WARNING: Allowing syntax incorrect UINT32 attribute to be presented UTF8 string");
Value::new_utf8(v)
})
}).collect()
}
ia => {
warn!("WARNING: Allowing invalid attribute {} to be interpretted as UTF8 string. YOU MAY ENCOUNTER ODD BEHAVIOUR!!!", ia);
vs.into_iter().map(|v| Value::new_utf8(v)).collect()
@ -349,8 +358,7 @@ impl Entry<EntryInvalid, EntryNew> {
impl<STATE> Entry<EntryInvalid, STATE> {
// This is only used in tests today, but I don't want to cfg test it.
#[allow(dead_code)]
fn get_uuid(&self) -> Option<&Uuid> {
pub(crate) fn get_uuid(&self) -> Option<&Uuid> {
match self.attrs.get("uuid") {
Some(vs) => match vs.iter().take(1).next() {
// Uv is a value that might contain uuid - we hope it does!

View file

@ -0,0 +1,277 @@
// A plugin that generates gid numbers on types that require them for posix
// support.
use crate::plugins::Plugin;
use crate::audit::AuditScope;
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew};
use crate::event::{CreateEvent, ModifyEvent};
// use crate::server::QueryServerTransaction;
use crate::server::QueryServerWriteTransaction;
use crate::utils::uuid_to_gid_u32;
use crate::value::{PartialValue, Value};
use kanidm_proto::v1::OperationError;
static GIDNUMBER_MIN: u32 = 2000;
lazy_static! {
static ref CLASS_POSIXGROUP: PartialValue = PartialValue::new_iutf8s("posixgroup");
static ref CLASS_POSIXACCOUNT: PartialValue = PartialValue::new_iutf8s("posixaccount");
}
pub struct GidNumber {}
fn apply_gidnumber<T: Copy>(
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 = try_audit!(au, e.get_uuid().ok_or(OperationError::InvalidEntryState));
let gid = uuid_to_gid_u32(u_ref);
// assert the value is greater than 2000
if gid < GIDNUMBER_MIN {
return Err(OperationError::InvalidAttribute(format!(
"gidnumber {} may overlap with system range {}",
gid, GIDNUMBER_MIN
)));
}
let gid_v = Value::new_uint32(gid);
audit_log!(au, "Generated {} for {:?}", gid, u_ref);
e.set_avas("gidnumber", vec![gid_v]);
Ok(())
} else {
Ok(())
}
}
impl Plugin for GidNumber {
fn id() -> &'static str {
"plugin_gidnumber"
}
fn pre_create_transform(
au: &mut AuditScope,
_qs: &mut 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: &mut 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, EntryInvalid, EntryNew};
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
use crate::value::{PartialValue, Value};
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<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
"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 that gid is not altered if provided on create.
#[test]
fn test_gidnumber_create_noaction() {
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
"attrs": {
"class": ["account", "posixaccount"],
"name": ["testperson"],
"uuid": ["83a0927f-3de1-45ec-bea0-2f7b997ef244"],
"gidnumber": ["1000"],
"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",
1000
)
);
}
// Test generated if not on mod (ie adding posixaccount to something)
#[test]
fn test_gidnumber_modify_generate() {
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
"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_iutf8s("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 generated if DELETED on mod
#[test]
fn test_gidnumber_modify_regenerate() {
let e: Entry<EntryInvalid, 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_iutf8s("testperson"))),
modlist!([m_purge("gidnumber")]),
None,
|au, qs_write: &QueryServerWriteTransaction| check_gid(
au,
qs_write,
"83a0927f-3de1-45ec-bea0-2f7b997ef244",
0x997ef244
)
);
}
// Test NOT altered if given on mod
#[test]
fn test_gidnumber_modify_noaction() {
let e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
r#"{
"valid": null,
"state": null,
"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_iutf8s("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
)
);
}
}

View file

@ -11,6 +11,7 @@ mod attrunique;
mod base;
mod domain;
mod failure;
mod gidnumber;
mod memberof;
mod protected;
mod recycle;
@ -281,9 +282,13 @@ impl Plugins {
) -> Result<(), OperationError> {
audit_segment!(au, || {
let res = run_pre_create_transform_plugin!(au, qs, cand, ce, base::Base)
.and_then(|_| {
run_pre_create_transform_plugin!(au, qs, cand, ce, gidnumber::GidNumber)
})
.and_then(|_| run_pre_create_transform_plugin!(au, qs, cand, ce, domain::Domain))
.and_then(|_| run_pre_create_transform_plugin!(au, qs, cand, ce, spn::Spn))
.and_then(|_| {
// Should always be last
run_pre_create_transform_plugin!(au, qs, cand, ce, attrunique::AttrUnique)
});
res
@ -326,7 +331,9 @@ impl Plugins {
audit_segment!(au, || {
let res = run_pre_modify_plugin!(au, qs, cand, me, protected::Protected)
.and_then(|_| run_pre_modify_plugin!(au, qs, cand, me, base::Base))
.and_then(|_| run_pre_modify_plugin!(au, qs, cand, me, gidnumber::GidNumber))
.and_then(|_| run_pre_modify_plugin!(au, qs, cand, me, spn::Spn))
// attr unique should always be last
.and_then(|_| run_pre_modify_plugin!(au, qs, cand, me, attrunique::AttrUnique));
res

View file

@ -238,6 +238,14 @@ impl SchemaAttribute {
}
}
fn validate_uint32(&self, v: &Value) -> Result<(), SchemaError> {
if v.is_uint32() {
Ok(())
} else {
Err(SchemaError::InvalidAttributeSyntax)
}
}
// TODO: There may be a difference between a value and a filter value on complex
// types - IE a complex type may have multiple parts that are secret, but a filter
// on that may only use a single tagged attribute for example.
@ -255,6 +263,7 @@ impl SchemaAttribute {
SyntaxType::RADIUS_UTF8STRING => v.is_radius_string(),
SyntaxType::SSHKEY => v.is_sshkey(),
SyntaxType::SERVICE_PRINCIPLE_NAME => v.is_spn(),
SyntaxType::UINT32 => v.is_uint32(),
};
if r {
Ok(())
@ -376,6 +385,13 @@ impl SchemaAttribute {
acc
}
}),
SyntaxType::UINT32 => ava.iter().fold(Ok(()), |acc, v| {
if acc.is_ok() {
self.validate_uint32(v)
} else {
acc
}
}),
}
}
}

View file

@ -458,6 +458,9 @@ pub trait QueryServerTransaction {
SyntaxType::RADIUS_UTF8STRING => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::SSHKEY => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())),
SyntaxType::SERVICE_PRINCIPLE_NAME => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set".to_string())),
SyntaxType::UINT32 => Value::new_uint32_str(value.as_str())
.ok_or(OperationError::InvalidAttribute("Invalid uint32 syntax".to_string())),
}
}
None => {
@ -536,6 +539,9 @@ pub trait QueryServerTransaction {
.ok_or(OperationError::InvalidAttribute(
"Invalid SPN syntax".to_string(),
)),
SyntaxType::UINT32 => PartialValue::new_uint32_str(value.as_str()).ok_or(
OperationError::InvalidAttribute("Invalid Uint32 syntax".to_string()),
),
}
}
None => {
@ -1611,10 +1617,13 @@ impl<'a> QueryServerWriteTransaction<'a> {
JSON_SCHEMA_ATTR_DOMAIN_NAME,
JSON_SCHEMA_ATTR_DOMAIN_UUID,
JSON_SCHEMA_ATTR_DOMAIN_SSID,
JSON_SCHEMA_ATTR_GIDNUMBER,
JSON_SCHEMA_CLASS_PERSON,
JSON_SCHEMA_CLASS_GROUP,
JSON_SCHEMA_CLASS_ACCOUNT,
JSON_SCHEMA_CLASS_DOMAIN_INFO,
JSON_SCHEMA_CLASS_POSIXACCOUNT,
JSON_SCHEMA_CLASS_POSIXGROUP,
];
let mut audit_si = AuditScope::new("start_initialise_schema_idm");

View file

@ -7,6 +7,13 @@ use rand::{thread_rng, Rng};
pub type SID = [u8; 4];
pub fn uuid_to_gid_u32(u: &Uuid) -> u32 {
let b_ref = u.as_bytes();
let mut x: [u8; 4] = [0; 4];
x.clone_from_slice(&b_ref[12..16]);
u32::from_be_bytes(x)
}
fn uuid_from_u64_u32(a: u64, b: u32, sid: &SID) -> Uuid {
let mut v: Vec<u8> = Vec::with_capacity(16);
v.extend_from_slice(&a.to_be_bytes());
@ -46,8 +53,9 @@ pub fn uuid_from_now(sid: &SID) -> Uuid {
#[cfg(test)]
mod tests {
use crate::utils::uuid_from_duration;
use crate::utils::{uuid_from_duration, uuid_to_gid_u32};
use std::time::Duration;
use uuid::Uuid;
#[test]
fn test_utils_uuid_from_duration() {
@ -63,4 +71,19 @@ mod tests {
u2.to_hyphenated().to_string()
);
}
#[test]
fn test_utils_uuid_to_gid_u32() {
let u1 = Uuid::parse_str("00000000-0000-0001-0000-000000000000").unwrap();
let r1 = uuid_to_gid_u32(&u1);
assert!(r1 == 0);
let u2 = Uuid::parse_str("00000000-0000-0001-0000-0000ffffffff").unwrap();
let r2 = uuid_to_gid_u32(&u2);
assert!(r2 == 0xffffffff);
let u3 = Uuid::parse_str("00000000-0000-0001-0000-ffff12345678").unwrap();
let r3 = uuid_to_gid_u32(&u3);
assert!(r3 == 0x12345678);
}
}

View file

@ -95,6 +95,7 @@ pub enum SyntaxType {
RADIUS_UTF8STRING,
SSHKEY,
SERVICE_PRINCIPLE_NAME,
UINT32,
}
impl TryFrom<&str> for SyntaxType {
@ -115,6 +116,7 @@ impl TryFrom<&str> for SyntaxType {
"RADIUS_UTF8STRING" => Ok(SyntaxType::RADIUS_UTF8STRING),
"SSHKEY" => Ok(SyntaxType::SSHKEY),
"SERVICE_PRINCIPLE_NAME" => Ok(SyntaxType::SERVICE_PRINCIPLE_NAME),
"UINT32" => Ok(SyntaxType::UINT32),
_ => Err(()),
}
}
@ -137,6 +139,7 @@ impl TryFrom<usize> for SyntaxType {
9 => Ok(SyntaxType::RADIUS_UTF8STRING),
10 => Ok(SyntaxType::SSHKEY),
11 => Ok(SyntaxType::SERVICE_PRINCIPLE_NAME),
12 => Ok(SyntaxType::UINT32),
_ => Err(()),
}
}
@ -157,6 +160,7 @@ impl SyntaxType {
SyntaxType::RADIUS_UTF8STRING => "RADIUS_UTF8STRING",
SyntaxType::SSHKEY => "SSHKEY",
SyntaxType::SERVICE_PRINCIPLE_NAME => "SERVICE_PRINCIPLE_NAME",
SyntaxType::UINT32 => "UINT32",
})
}
@ -174,6 +178,7 @@ impl SyntaxType {
SyntaxType::RADIUS_UTF8STRING => 9,
SyntaxType::SSHKEY => 10,
SyntaxType::SERVICE_PRINCIPLE_NAME => 11,
SyntaxType::UINT32 => 12,
}
}
}
@ -202,6 +207,7 @@ pub enum PartialValue {
SshKey(String),
RadiusCred,
Spn(String, String),
Uint32(u32),
}
impl PartialValue {
@ -414,6 +420,23 @@ impl PartialValue {
}
}
pub fn new_uint32(u: u32) -> Self {
PartialValue::Uint32(u)
}
pub fn new_uint32_str(u: &str) -> Option<Self> {
u32::from_str_radix(u, 10)
.ok()
.map(|uv| PartialValue::Uint32(uv))
}
pub fn is_uint32(&self) -> bool {
match self {
PartialValue::Uint32(_) => true,
_ => false,
}
}
pub fn to_str(&self) -> Option<&str> {
match self {
PartialValue::Utf8(s) => Some(s.as_str()),
@ -449,6 +472,7 @@ impl PartialValue {
PartialValue::RadiusCred => "_".to_string(),
PartialValue::SshKey(tag) => tag.to_string(),
PartialValue::Spn(name, realm) => format!("{}@{}", name, realm),
PartialValue::Uint32(u) => u.to_string(),
}
}
@ -863,6 +887,24 @@ impl Value {
}
}
pub fn new_uint32(u: u32) -> Self {
Value {
pv: PartialValue::new_uint32(u),
data: None,
}
}
pub fn new_uint32_str(u: &str) -> Option<Self> {
PartialValue::new_uint32_str(u).map(|ui| Value { pv: ui, data: None })
}
pub fn is_uint32(&self) -> bool {
match &self.pv {
PartialValue::Uint32(_) => true,
_ => false,
}
}
pub fn contains(&self, s: &PartialValue) -> bool {
self.pv.contains(s)
}
@ -932,6 +974,10 @@ impl Value {
pv: PartialValue::Spn(n, r),
data: None,
}),
DbValueV1::UI(u) => Ok(Value {
pv: PartialValue::Uint32(u),
data: None,
}),
}
}
@ -989,6 +1035,7 @@ impl Value {
})
}
PartialValue::Spn(n, r) => DbValueV1::SP(n.clone(), r.clone()),
PartialValue::Uint32(u) => DbValueV1::UI(u.clone()),
}
}
@ -1096,6 +1143,7 @@ impl Value {
// interfaces.
PartialValue::RadiusCred => "radius".to_string(),
PartialValue::Spn(n, r) => format!("{}@{}", n, r),
PartialValue::Uint32(u) => u.to_string(),
}
}
@ -1154,6 +1202,7 @@ impl Value {
}
PartialValue::RadiusCred => vec![],
PartialValue::Spn(n, r) => vec![format!("{}@{}", n, r)],
PartialValue::Uint32(u) => vec![u.to_string()],
}
}
}
@ -1270,6 +1319,20 @@ mod tests {
assert!("claire@example.net.au" == spnv.to_proto_string_clone());
}
#[test]
fn test_value_uint32() {
assert!(Value::new_uint32_str("test").is_none());
assert!(Value::new_uint32_str("18446744073709551615").is_none());
let u32v = Value::new_uint32_str("4000").unwrap();
let u32pv = PartialValue::new_uint32_str("4000").unwrap();
let idx_key = u32pv.get_idx_eq_key();
let vidx_key = u32v.generate_idx_eq_keys().pop().unwrap();
assert!(idx_key == vidx_key);
}
/*
#[test]
fn test_schema_syntax_json_filter() {