mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 04:57:00 +01:00
Add email syntax (#465)
Part one of #461 - this adds the syntax to support email addresses and validation of their content, and a method to serialise to the DB that can be extended with attribute tagging in the future. Part two will address administration of these values.
This commit is contained in:
parent
7da4fa9d7e
commit
ea34dc08a9
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -1778,6 +1778,7 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"users",
|
"users",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"validator",
|
||||||
"webauthn-authenticator-rs",
|
"webauthn-authenticator-rs",
|
||||||
"webauthn-rs",
|
"webauthn-rs",
|
||||||
"zxcvbn",
|
"zxcvbn",
|
||||||
|
@ -3609,6 +3610,28 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "validator"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be110dc66fa015b8b1d2c4eae40c495a27fae55f82b9cae3efb8178241ed20eb"
|
||||||
|
dependencies = [
|
||||||
|
"idna",
|
||||||
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
"validator_types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "validator_types"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad9680608df133af2c1ddd5eaf1ddce91d60d61b6bc51494ef326458365a470a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "value-bag"
|
name = "value-bag"
|
||||||
version = "1.0.0-alpha.6"
|
version = "1.0.0-alpha.6"
|
||||||
|
|
|
@ -829,6 +829,17 @@ impl KanidmAsyncClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn idm_account_add_attr(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
attr: &str,
|
||||||
|
values: &[&str],
|
||||||
|
) -> Result<bool, ClientError> {
|
||||||
|
let msg: Vec<_> = values.iter().map(|v| (*v).to_string()).collect();
|
||||||
|
self.perform_post_request(format!("/v1/account/{}/_attr/{}", id, attr).as_str(), msg)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn idm_account_set_attr(
|
pub async fn idm_account_set_attr(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
|
|
|
@ -571,6 +571,15 @@ impl KanidmClient {
|
||||||
tokio_block_on(self.asclient.idm_account_purge_attr(id, attr))
|
tokio_block_on(self.asclient.idm_account_purge_attr(id, attr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn idm_account_add_attr(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
attr: &str,
|
||||||
|
values: &[&str],
|
||||||
|
) -> Result<bool, ClientError> {
|
||||||
|
tokio_block_on(self.asclient.idm_account_add_attr(id, attr, values))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn idm_account_set_attr(
|
pub fn idm_account_set_attr(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
|
|
|
@ -98,7 +98,8 @@ fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Option<boo
|
||||||
),
|
),
|
||||||
entry => {
|
entry => {
|
||||||
let new_value = match entry {
|
let new_value = match entry {
|
||||||
"acp_receiver" => "{\"eq\":[\"memberof\",\"00000000-0000-0000-0000-000000000011\"]}".to_string(),
|
"mail" => format!("{}@example.com", id),
|
||||||
|
"acp_receiver" => r#"{"eq":["memberof","00000000-0000-0000-0000-000000000011"]}"#.to_string(),
|
||||||
"acp_targetscope" => "{\"and\": [{\"eq\": [\"class\",\"access_control_profile\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}".to_string(),
|
"acp_targetscope" => "{\"and\": [{\"eq\": [\"class\",\"access_control_profile\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}".to_string(),
|
||||||
_ => id.to_string(),
|
_ => id.to_string(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -425,6 +425,17 @@ fn test_server_rest_account_lifecycle() {
|
||||||
.idm_account_set_displayname("demo_account", "Demo Account")
|
.idm_account_set_displayname("demo_account", "Demo Account")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// Test adding some mail addrs
|
||||||
|
rsclient
|
||||||
|
.idm_account_add_attr("demo_account", "mail", &["demo@example.com"])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let r = rsclient
|
||||||
|
.idm_account_get_attr("demo_account", "mail")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(r == Some(vec!["demo@example.com".to_string()]));
|
||||||
|
|
||||||
// Delete the account
|
// Delete the account
|
||||||
rsclient.idm_account_delete("demo_account").unwrap();
|
rsclient.idm_account_delete("demo_account").unwrap();
|
||||||
});
|
});
|
||||||
|
|
|
@ -86,6 +86,8 @@ users = "0.11"
|
||||||
|
|
||||||
smartstring = { version = "0.2", features = ["serde"] }
|
smartstring = { version = "0.2", features = ["serde"] }
|
||||||
|
|
||||||
|
validator = { version = "0.13" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
simd_support = [ "concread/simd_support" ]
|
simd_support = [ "concread/simd_support" ]
|
||||||
# default = [ "libsqlite3-sys/bundled", "openssl/vendored" ]
|
# default = [ "libsqlite3-sys/bundled", "openssl/vendored" ]
|
||||||
|
|
|
@ -80,6 +80,11 @@ pub struct DbValueTaggedStringV1 {
|
||||||
pub d: String,
|
pub d: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DbValueEmailAddressV1 {
|
||||||
|
pub d: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub enum DbValueV1 {
|
pub enum DbValueV1 {
|
||||||
U8(String),
|
U8(String),
|
||||||
|
@ -99,6 +104,7 @@ pub enum DbValueV1 {
|
||||||
CI(DbCidV1),
|
CI(DbCidV1),
|
||||||
NU(String),
|
NU(String),
|
||||||
DT(String),
|
DT(String),
|
||||||
|
EM(DbValueEmailAddressV1),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub const JSON_SCHEMA_ATTR_MAIL: &str = r#"
|
||||||
"mail"
|
"mail"
|
||||||
],
|
],
|
||||||
"syntax": [
|
"syntax": [
|
||||||
"UTF8STRING"
|
"EMAIL_ADDRESS"
|
||||||
],
|
],
|
||||||
"uuid": [
|
"uuid": [
|
||||||
"00000000-0000-0000-0000-ffff00000041"
|
"00000000-0000-0000-0000-ffff00000041"
|
||||||
|
@ -561,7 +561,8 @@ pub const JSON_SCHEMA_CLASS_ACCOUNT: &str = r#"
|
||||||
"ssh_publickey",
|
"ssh_publickey",
|
||||||
"radius_secret",
|
"radius_secret",
|
||||||
"account_expire",
|
"account_expire",
|
||||||
"account_valid_from"
|
"account_valid_from",
|
||||||
|
"mail"
|
||||||
],
|
],
|
||||||
"systemmust": [
|
"systemmust": [
|
||||||
"displayname",
|
"displayname",
|
||||||
|
|
|
@ -1204,7 +1204,9 @@ pub fn create_https_server(
|
||||||
tserver.at("/status").get(self::status);
|
tserver.at("/status").get(self::status);
|
||||||
|
|
||||||
let mut well_known = tserver.at("/well-known");
|
let mut well_known = tserver.at("/well-known");
|
||||||
well_known.at("/openid-configuration").get(get_openid_configuration);
|
well_known
|
||||||
|
.at("/openid-configuration")
|
||||||
|
.get(get_openid_configuration);
|
||||||
|
|
||||||
let mut raw_route = tserver.at("/v1/raw");
|
let mut raw_route = tserver.at("/v1/raw");
|
||||||
raw_route.at("/create").post(create);
|
raw_route.at("/create").post(create);
|
||||||
|
|
|
@ -73,7 +73,7 @@ struct CredWebauthn {
|
||||||
|
|
||||||
/// The current active handler for this authentication session. This is determined from what credentials
|
/// The current active handler for this authentication session. This is determined from what credentials
|
||||||
/// are possible from the account, and what the user selected as the preferred authentication
|
/// are possible from the account, and what the user selected as the preferred authentication
|
||||||
/// mechanism.
|
/// mechanism.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum CredHandler {
|
enum CredHandler {
|
||||||
Anonymous,
|
Anonymous,
|
||||||
|
|
|
@ -200,6 +200,7 @@ impl SchemaAttribute {
|
||||||
SyntaxType::Cid => v.is_cid(),
|
SyntaxType::Cid => v.is_cid(),
|
||||||
SyntaxType::NsUniqueId => v.is_nsuniqueid(),
|
SyntaxType::NsUniqueId => v.is_nsuniqueid(),
|
||||||
SyntaxType::DateTime => v.is_datetime(),
|
SyntaxType::DateTime => v.is_datetime(),
|
||||||
|
SyntaxType::EmailAddress => v.is_email_address(),
|
||||||
};
|
};
|
||||||
if r {
|
if r {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -229,161 +230,30 @@ impl SchemaAttribute {
|
||||||
return Err(SchemaError::InvalidAttributeSyntax(a.to_string()));
|
return Err(SchemaError::InvalidAttributeSyntax(a.to_string()));
|
||||||
};
|
};
|
||||||
// If syntax, check the type is correct
|
// If syntax, check the type is correct
|
||||||
match self.syntax {
|
let valid = match self.syntax {
|
||||||
SyntaxType::Boolean => ava.iter().fold(Ok(()), |acc, v| {
|
SyntaxType::Boolean => ava.iter().all(Value::is_bool),
|
||||||
acc.and_then(|_| {
|
SyntaxType::SYNTAX_ID => ava.iter().all(Value::is_syntax),
|
||||||
if v.is_bool() {
|
SyntaxType::Uuid => ava.iter().all(Value::is_uuid),
|
||||||
Ok(())
|
SyntaxType::REFERENCE_UUID => ava.iter().all(Value::is_refer),
|
||||||
} else {
|
SyntaxType::INDEX_ID => ava.iter().all(Value::is_index),
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
SyntaxType::Utf8StringInsensitive => ava.iter().all(Value::is_insensitive_utf8),
|
||||||
}
|
SyntaxType::Utf8StringIname => ava.iter().all(Value::is_iname),
|
||||||
})
|
SyntaxType::UTF8STRING => ava.iter().all(Value::is_utf8),
|
||||||
}),
|
SyntaxType::JSON_FILTER => ava.iter().all(Value::is_json_filter),
|
||||||
SyntaxType::SYNTAX_ID => ava.iter().fold(Ok(()), |acc, v| {
|
SyntaxType::Credential => ava.iter().all(Value::is_credential),
|
||||||
acc.and_then(|_| {
|
SyntaxType::RadiusUtf8String => ava.iter().all(Value::is_radius_string),
|
||||||
if v.is_syntax() {
|
SyntaxType::SshKey => ava.iter().all(Value::is_sshkey),
|
||||||
Ok(())
|
SyntaxType::SecurityPrincipalName => ava.iter().all(Value::is_spn),
|
||||||
} else {
|
SyntaxType::UINT32 => ava.iter().all(Value::is_uint32),
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
SyntaxType::Cid => ava.iter().all(Value::is_cid),
|
||||||
}
|
SyntaxType::NsUniqueId => ava.iter().all(Value::is_nsuniqueid),
|
||||||
})
|
SyntaxType::DateTime => ava.iter().all(Value::is_datetime),
|
||||||
}),
|
SyntaxType::EmailAddress => ava.iter().all(Value::is_email_address),
|
||||||
SyntaxType::Uuid => ava.iter().fold(Ok(()), |acc, v| {
|
};
|
||||||
acc.and_then(|_| {
|
if valid {
|
||||||
if v.is_uuid() {
|
Ok(())
|
||||||
Ok(())
|
} else {
|
||||||
} else {
|
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
// This is the same as a UUID, refint is a plugin
|
|
||||||
SyntaxType::REFERENCE_UUID => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_refer() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::INDEX_ID => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_index() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::Utf8StringInsensitive => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_insensitive_utf8() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::Utf8StringIname => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_iname() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::UTF8STRING => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_utf8() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::JSON_FILTER => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_json_filter() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::Credential => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_credential() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::RadiusUtf8String => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_radius_string() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::SshKey => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_sshkey() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::SecurityPrincipalName => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_spn() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::UINT32 => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_uint32() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::Cid => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_cid() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::NsUniqueId => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_nsuniqueid() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
SyntaxType::DateTime => ava.iter().fold(Ok(()), |acc, v| {
|
|
||||||
acc.and_then(|_| {
|
|
||||||
if v.is_datetime() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1206,7 +1076,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: true,
|
phantom: true,
|
||||||
index: vec![],
|
index: vec![],
|
||||||
syntax: SyntaxType::UTF8STRING,
|
syntax: SyntaxType::EmailAddress,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(
|
self.attributes.insert(
|
||||||
|
@ -1219,7 +1089,7 @@ impl<'a> SchemaWriteTransaction<'a> {
|
||||||
unique: false,
|
unique: false,
|
||||||
phantom: true,
|
phantom: true,
|
||||||
index: vec![],
|
index: vec![],
|
||||||
syntax: SyntaxType::UTF8STRING,
|
syntax: SyntaxType::EmailAddress,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.attributes.insert(
|
self.attributes.insert(
|
||||||
|
|
|
@ -537,6 +537,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
SyntaxType::NsUniqueId => Ok(Value::new_nsuniqueid_s(value)),
|
SyntaxType::NsUniqueId => Ok(Value::new_nsuniqueid_s(value)),
|
||||||
SyntaxType::DateTime => Value::new_datetime_s(value)
|
SyntaxType::DateTime => Value::new_datetime_s(value)
|
||||||
.ok_or_else(|| OperationError::InvalidAttribute("Invalid DateTime (rfc3339) syntax".to_string())),
|
.ok_or_else(|| OperationError::InvalidAttribute("Invalid DateTime (rfc3339) syntax".to_string())),
|
||||||
|
SyntaxType::EmailAddress => Ok(Value::new_email_address_s(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -629,6 +630,7 @@ pub trait QueryServerTransaction<'a> {
|
||||||
"Invalid DateTime (rfc3339) syntax".to_string(),
|
"Invalid DateTime (rfc3339) syntax".to_string(),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
SyntaxType::EmailAddress => Ok(PartialValue::new_email_address_s(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
//! typed values, allows their comparison, filtering and more. It also has the code for serialising
|
//! typed values, allows their comparison, filtering and more. It also has the code for serialising
|
||||||
//! these into a form for the backend that can be persistent into the [`Backend`].
|
//! these into a form for the backend that can be persistent into the [`Backend`].
|
||||||
|
|
||||||
use crate::be::dbvalue::{DbCidV1, DbValueCredV1, DbValueTaggedStringV1, DbValueV1};
|
use crate::be::dbvalue::{
|
||||||
|
DbCidV1, DbValueCredV1, DbValueEmailAddressV1, DbValueTaggedStringV1, DbValueV1,
|
||||||
|
};
|
||||||
use crate::credential::Credential;
|
use crate::credential::Credential;
|
||||||
use crate::repl::cid::Cid;
|
use crate::repl::cid::Cid;
|
||||||
use kanidm_proto::v1::Filter as ProtoFilter;
|
use kanidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
@ -133,6 +135,7 @@ pub enum SyntaxType {
|
||||||
Cid,
|
Cid,
|
||||||
NsUniqueId,
|
NsUniqueId,
|
||||||
DateTime,
|
DateTime,
|
||||||
|
EmailAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for SyntaxType {
|
impl TryFrom<&str> for SyntaxType {
|
||||||
|
@ -158,6 +161,7 @@ impl TryFrom<&str> for SyntaxType {
|
||||||
"CID" => Ok(SyntaxType::Cid),
|
"CID" => Ok(SyntaxType::Cid),
|
||||||
"NSUNIQUEID" => Ok(SyntaxType::NsUniqueId),
|
"NSUNIQUEID" => Ok(SyntaxType::NsUniqueId),
|
||||||
"DATETIME" => Ok(SyntaxType::DateTime),
|
"DATETIME" => Ok(SyntaxType::DateTime),
|
||||||
|
"EMAIL_ADDRESS" => Ok(SyntaxType::EmailAddress),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,6 +189,7 @@ impl TryFrom<usize> for SyntaxType {
|
||||||
14 => Ok(SyntaxType::Utf8StringIname),
|
14 => Ok(SyntaxType::Utf8StringIname),
|
||||||
15 => Ok(SyntaxType::NsUniqueId),
|
15 => Ok(SyntaxType::NsUniqueId),
|
||||||
16 => Ok(SyntaxType::DateTime),
|
16 => Ok(SyntaxType::DateTime),
|
||||||
|
17 => Ok(SyntaxType::EmailAddress),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,35 +215,33 @@ impl SyntaxType {
|
||||||
SyntaxType::Utf8StringIname => 14,
|
SyntaxType::Utf8StringIname => 14,
|
||||||
SyntaxType::NsUniqueId => 15,
|
SyntaxType::NsUniqueId => 15,
|
||||||
SyntaxType::DateTime => 16,
|
SyntaxType::DateTime => 16,
|
||||||
|
SyntaxType::EmailAddress => 17,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SyntaxType {
|
impl fmt::Display for SyntaxType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(
|
f.write_str(match self {
|
||||||
f,
|
SyntaxType::UTF8STRING => "UTF8STRING",
|
||||||
"{}",
|
SyntaxType::Utf8StringInsensitive => "UTF8STRING_INSENSITIVE",
|
||||||
match self {
|
SyntaxType::Utf8StringIname => "UTF8STRING_INAME",
|
||||||
SyntaxType::UTF8STRING => "UTF8STRING",
|
SyntaxType::Uuid => "UUID",
|
||||||
SyntaxType::Utf8StringInsensitive => "UTF8STRING_INSENSITIVE",
|
SyntaxType::Boolean => "BOOLEAN",
|
||||||
SyntaxType::Utf8StringIname => "UTF8STRING_INAME",
|
SyntaxType::SYNTAX_ID => "SYNTAX_ID",
|
||||||
SyntaxType::Uuid => "UUID",
|
SyntaxType::INDEX_ID => "INDEX_ID",
|
||||||
SyntaxType::Boolean => "BOOLEAN",
|
SyntaxType::REFERENCE_UUID => "REFERENCE_UUID",
|
||||||
SyntaxType::SYNTAX_ID => "SYNTAX_ID",
|
SyntaxType::JSON_FILTER => "JSON_FILTER",
|
||||||
SyntaxType::INDEX_ID => "INDEX_ID",
|
SyntaxType::Credential => "CREDENTIAL",
|
||||||
SyntaxType::REFERENCE_UUID => "REFERENCE_UUID",
|
SyntaxType::RadiusUtf8String => "RADIUS_UTF8STRING",
|
||||||
SyntaxType::JSON_FILTER => "JSON_FILTER",
|
SyntaxType::SshKey => "SSHKEY",
|
||||||
SyntaxType::Credential => "CREDENTIAL",
|
SyntaxType::SecurityPrincipalName => "SECURITY_PRINCIPAL_NAME",
|
||||||
SyntaxType::RadiusUtf8String => "RADIUS_UTF8STRING",
|
SyntaxType::UINT32 => "UINT32",
|
||||||
SyntaxType::SshKey => "SSHKEY",
|
SyntaxType::Cid => "CID",
|
||||||
SyntaxType::SecurityPrincipalName => "SECURITY_PRINCIPAL_NAME",
|
SyntaxType::NsUniqueId => "NSUNIQUEID",
|
||||||
SyntaxType::UINT32 => "UINT32",
|
SyntaxType::DateTime => "DATETIME",
|
||||||
SyntaxType::Cid => "CID",
|
SyntaxType::EmailAddress => "EMAIL_ADDRESS",
|
||||||
SyntaxType::NsUniqueId => "NSUNIQUEID",
|
})
|
||||||
SyntaxType::DateTime => "DATETIME",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +262,12 @@ impl std::fmt::Debug for DataValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A partial value is a key or key subset that can be used to match for equality or substring
|
||||||
|
/// against a complete Value within a set in an Entry.
|
||||||
|
///
|
||||||
|
/// A partialValue is typically used when you need to match against a value, but without
|
||||||
|
/// requiring all of it's data or expression. This is common in Filters or other direct
|
||||||
|
/// lookups and requests.
|
||||||
#[derive(Hash, Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)]
|
#[derive(Hash, Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum PartialValue {
|
pub enum PartialValue {
|
||||||
Utf8(String),
|
Utf8(String),
|
||||||
|
@ -281,6 +290,7 @@ pub enum PartialValue {
|
||||||
Cid(Cid),
|
Cid(Cid),
|
||||||
Nsuniqueid(String),
|
Nsuniqueid(String),
|
||||||
DateTime(OffsetDateTime),
|
DateTime(OffsetDateTime),
|
||||||
|
EmailAddress(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialValue {
|
impl PartialValue {
|
||||||
|
@ -510,6 +520,14 @@ impl PartialValue {
|
||||||
matches!(self, PartialValue::DateTime(_))
|
matches!(self, PartialValue::DateTime(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_email_address_s(s: &str) -> Self {
|
||||||
|
PartialValue::EmailAddress(s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_email_address(&self) -> bool {
|
||||||
|
matches!(self, PartialValue::EmailAddress(_))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_str(&self) -> Option<&str> {
|
pub fn to_str(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
PartialValue::Utf8(s) => Some(s.as_str()),
|
PartialValue::Utf8(s) => Some(s.as_str()),
|
||||||
|
@ -541,7 +559,8 @@ impl PartialValue {
|
||||||
PartialValue::Utf8(s)
|
PartialValue::Utf8(s)
|
||||||
| PartialValue::Iutf8(s)
|
| PartialValue::Iutf8(s)
|
||||||
| PartialValue::Iname(s)
|
| PartialValue::Iname(s)
|
||||||
| PartialValue::Nsuniqueid(s) => s.clone(),
|
| PartialValue::Nsuniqueid(s)
|
||||||
|
| PartialValue::EmailAddress(s) => s.clone(),
|
||||||
PartialValue::Refer(u) | PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(),
|
PartialValue::Refer(u) | PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(),
|
||||||
PartialValue::Bool(b) => b.to_string(),
|
PartialValue::Bool(b) => b.to_string(),
|
||||||
PartialValue::Syntax(syn) => syn.to_string(),
|
PartialValue::Syntax(syn) => syn.to_string(),
|
||||||
|
@ -571,6 +590,12 @@ impl PartialValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A value is a complete unit of data for an attribute. It is made up of a PartialValue, which is
|
||||||
|
/// used for selection, filtering, searching, matching etc. It also contains supplemental data
|
||||||
|
/// which may be stored inside of the Value, such as credential secrets, blobs etc.
|
||||||
|
///
|
||||||
|
/// This type is used when you need the "full data" of an attribute. Typically this is in a create
|
||||||
|
/// or modification operation where you are applying a set of complete values into an entry.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Value {
|
pub struct Value {
|
||||||
pv: PartialValue,
|
pv: PartialValue,
|
||||||
|
@ -1006,6 +1031,17 @@ impl Value {
|
||||||
self.pv.is_datetime()
|
self.pv.is_datetime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_email_address_s(s: &str) -> Self {
|
||||||
|
Value {
|
||||||
|
pv: PartialValue::new_email_address_s(s),
|
||||||
|
data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_email_address(&self) -> bool {
|
||||||
|
self.pv.is_email_address()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains(&self, s: &PartialValue) -> bool {
|
pub fn contains(&self, s: &PartialValue) -> bool {
|
||||||
self.pv.contains(s)
|
self.pv.contains(s)
|
||||||
}
|
}
|
||||||
|
@ -1101,6 +1137,10 @@ impl Value {
|
||||||
DbValueV1::DT(s) => PartialValue::new_datetime_s(&s)
|
DbValueV1::DT(s) => PartialValue::new_datetime_s(&s)
|
||||||
.ok_or(())
|
.ok_or(())
|
||||||
.map(|pv| Value { pv, data: None }),
|
.map(|pv| Value { pv, data: None }),
|
||||||
|
DbValueV1::EM(DbValueEmailAddressV1 { d }) => Ok(Value {
|
||||||
|
pv: PartialValue::EmailAddress(d),
|
||||||
|
data: None,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1172,6 +1212,9 @@ impl Value {
|
||||||
debug_assert!(odt.offset() == time::UtcOffset::UTC);
|
debug_assert!(odt.offset() == time::UtcOffset::UTC);
|
||||||
DbValueV1::DT(odt.format(time::Format::Rfc3339))
|
DbValueV1::DT(odt.format(time::Format::Rfc3339))
|
||||||
}
|
}
|
||||||
|
PartialValue::EmailAddress(mail) => {
|
||||||
|
DbValueV1::EM(DbValueEmailAddressV1 { d: mail.clone() })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1258,7 +1301,8 @@ impl Value {
|
||||||
PartialValue::Utf8(s)
|
PartialValue::Utf8(s)
|
||||||
| PartialValue::Iutf8(s)
|
| PartialValue::Iutf8(s)
|
||||||
| PartialValue::Iname(s)
|
| PartialValue::Iname(s)
|
||||||
| PartialValue::Nsuniqueid(s) => s.clone(),
|
| PartialValue::Nsuniqueid(s)
|
||||||
|
| PartialValue::EmailAddress(s) => s.clone(),
|
||||||
PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(),
|
PartialValue::Uuid(u) => u.to_hyphenated_ref().to_string(),
|
||||||
PartialValue::Bool(b) => b.to_string(),
|
PartialValue::Bool(b) => b.to_string(),
|
||||||
PartialValue::Syntax(syn) => syn.to_string(),
|
PartialValue::Syntax(syn) => syn.to_string(),
|
||||||
|
@ -1339,6 +1383,7 @@ impl Value {
|
||||||
},
|
},
|
||||||
PartialValue::Nsuniqueid(s) => NSUNIQUEID_RE.is_match(s),
|
PartialValue::Nsuniqueid(s) => NSUNIQUEID_RE.is_match(s),
|
||||||
PartialValue::DateTime(odt) => odt.offset() == time::UtcOffset::UTC,
|
PartialValue::DateTime(odt) => odt.offset() == time::UtcOffset::UTC,
|
||||||
|
PartialValue::EmailAddress(mail) => validator::validate_email(mail.as_str()),
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1349,7 +1394,8 @@ impl Value {
|
||||||
PartialValue::Utf8(s)
|
PartialValue::Utf8(s)
|
||||||
| PartialValue::Iutf8(s)
|
| PartialValue::Iutf8(s)
|
||||||
| PartialValue::Iname(s)
|
| PartialValue::Iname(s)
|
||||||
| PartialValue::Nsuniqueid(s) => vec![s.clone()],
|
| PartialValue::Nsuniqueid(s)
|
||||||
|
| PartialValue::EmailAddress(s) => vec![s.clone()],
|
||||||
PartialValue::Refer(u) | PartialValue::Uuid(u) => {
|
PartialValue::Refer(u) | PartialValue::Uuid(u) => {
|
||||||
vec![u.to_hyphenated_ref().to_string()]
|
vec![u.to_hyphenated_ref().to_string()]
|
||||||
}
|
}
|
||||||
|
@ -1590,6 +1636,22 @@ mod tests {
|
||||||
assert!(val3.validate());
|
assert!(val3.validate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_value_email_address() {
|
||||||
|
// https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address
|
||||||
|
let val1 = Value::new_email_address_s("william@blackhats.net.au");
|
||||||
|
let val2 = Value::new_email_address_s("alice@idm.example.com");
|
||||||
|
let val3 = Value::new_email_address_s("test+mailbox@foo.com");
|
||||||
|
let inv1 = Value::new_email_address_s("william");
|
||||||
|
let inv2 = Value::new_email_address_s("test~uuid");
|
||||||
|
|
||||||
|
assert!(!inv1.validate());
|
||||||
|
assert!(!inv2.validate());
|
||||||
|
assert!(val1.validate());
|
||||||
|
assert!(val2.validate());
|
||||||
|
assert!(val3.validate());
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#[test]
|
#[test]
|
||||||
fn test_schema_syntax_json_filter() {
|
fn test_schema_syntax_json_filter() {
|
||||||
|
|
Loading…
Reference in a new issue