ldap: lowercase all DN, fields, values

This commit is contained in:
Valentin Tolmer 2022-05-30 17:59:02 +02:00 committed by nitnelave
parent 7e62cc6eda
commit 1d8582f937

View File

@ -39,6 +39,7 @@ where
} }
fn parse_distinguished_name(dn: &str) -> Result<Vec<(String, String)>> { fn parse_distinguished_name(dn: &str) -> Result<Vec<(String, String)>> {
assert!(dn == dn.to_ascii_lowercase());
dn.split(',') dn.split(',')
.map(|s| make_dn_pair(s.split('=').map(str::trim).map(String::from))) .map(|s| make_dn_pair(s.split('=').map(str::trim).map(String::from)))
.collect() .collect()
@ -102,7 +103,7 @@ fn get_user_id_from_distinguished_name(
} }
fn get_user_attribute(user: &User, attribute: &str, dn: &str) -> Result<Option<Vec<String>>> { fn get_user_attribute(user: &User, attribute: &str, dn: &str) -> Result<Option<Vec<String>>> {
Ok(Some(match attribute.to_lowercase().as_str() { Ok(Some(match attribute.to_ascii_lowercase().as_str() {
"objectclass" => vec![ "objectclass" => vec![
"inetOrgPerson".to_string(), "inetOrgPerson".to_string(),
"posixAccount".to_string(), "posixAccount".to_string(),
@ -191,7 +192,7 @@ fn get_group_attribute(
attribute: &str, attribute: &str,
user_filter: &Option<&UserId>, user_filter: &Option<&UserId>,
) -> Result<Option<Vec<String>>> { ) -> Result<Option<Vec<String>>> {
Ok(Some(match attribute.to_lowercase().as_str() { Ok(Some(match attribute.to_ascii_lowercase().as_str() {
"objectclass" => vec!["groupOfUniqueNames".to_string()], "objectclass" => vec!["groupOfUniqueNames".to_string()],
"dn" | "distinguishedname" => vec![format!( "dn" | "distinguishedname" => vec![format!(
"cn={},ou=groups,{}", "cn={},ou=groups,{}",
@ -250,6 +251,14 @@ fn make_ldap_search_group_result_entry(
} }
fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)]) -> bool { fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)]) -> bool {
for (k, v) in subtree {
assert!(k == &k.to_ascii_lowercase());
assert!(v == &v.to_ascii_lowercase());
}
for (k, v) in base_tree {
assert!(k == &k.to_ascii_lowercase());
assert!(v == &v.to_ascii_lowercase());
}
if subtree.len() < base_tree.len() { if subtree.len() < base_tree.len() {
return false; return false;
} }
@ -263,22 +272,20 @@ fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)]) -> b
} }
fn map_field(field: &str) -> Result<String> { fn map_field(field: &str) -> Result<String> {
assert!(field == field.to_ascii_lowercase());
Ok(if field == "uid" { Ok(if field == "uid" {
"user_id".to_string() "user_id".to_string()
} else if field == "mail" { } else if field == "mail" {
"email".to_string() "email".to_string()
} else if field == "cn" || field.to_lowercase() == "displayname" { } else if field == "cn" || field == "displayname" {
"display_name".to_string() "display_name".to_string()
} else if field.to_lowercase() == "givenname" { } else if field == "givenname" {
"first_name".to_string() "first_name".to_string()
} else if field == "sn" { } else if field == "sn" {
"last_name".to_string() "last_name".to_string()
} else if field == "avatar" { } else if field == "avatar" {
"avatar".to_string() "avatar".to_string()
} else if field.to_lowercase() == "creationdate" } else if field == "creationdate" || field == "createtimestamp" || field == "modifytimestamp" {
|| field.to_lowercase() == "createtimestamp"
|| field.to_lowercase() == "modifytimestamp"
{
"creation_date".to_string() "creation_date".to_string()
} else { } else {
bail!("Unknown field: {}", field); bail!("Unknown field: {}", field);
@ -357,7 +364,8 @@ pub struct LdapHandler<Backend: BackendHandler + LoginHandler + OpaqueHandler> {
} }
impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend> { impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend> {
pub fn new(backend_handler: Backend, ldap_base_dn: String) -> Self { pub fn new(backend_handler: Backend, mut ldap_base_dn: String) -> Self {
ldap_base_dn.make_ascii_lowercase();
Self { Self {
user_info: None, user_info: None,
backend_handler, backend_handler,
@ -374,7 +382,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
pub async fn do_bind(&mut self, request: &LdapBindRequest) -> (LdapResultCode, String) { pub async fn do_bind(&mut self, request: &LdapBindRequest) -> (LdapResultCode, String) {
debug!(r#"Received bind request for "{}""#, &request.dn); debug!(r#"Received bind request for "{}""#, &request.dn);
let user_id = match get_user_id_from_distinguished_name( let user_id = match get_user_id_from_distinguished_name(
&request.dn, &request.dn.to_ascii_lowercase(),
&self.base_dn, &self.base_dn,
&self.base_dn_str, &self.base_dn_str,
) { ) {
@ -514,7 +522,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
return vec![root_dse_response(&self.base_dn_str), make_search_success()]; return vec![root_dse_response(&self.base_dn_str), make_search_success()];
} }
debug!("Received search request: {:?}", &request); debug!("Received search request: {:?}", &request);
let dn_parts = match parse_distinguished_name(&request.base) { let dn_parts = match parse_distinguished_name(&request.base.to_ascii_lowercase()) {
Ok(dn) => dn, Ok(dn) => dn,
Err(_) => { Err(_) => {
return vec![make_search_error( return vec![make_search_error(
@ -685,15 +693,17 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
fn convert_group_filter(&self, filter: &LdapFilter) -> Result<GroupRequestFilter> { fn convert_group_filter(&self, filter: &LdapFilter) -> Result<GroupRequestFilter> {
match filter { match filter {
LdapFilter::Equality(field, value) => { LdapFilter::Equality(field, value) => {
if field == "member" || field.to_lowercase() == "uniquemember" { let field = &field.to_ascii_lowercase();
let value = &value.to_ascii_lowercase();
if field == "member" || field == "uniquemember" {
let user_name = get_user_id_from_distinguished_name( let user_name = get_user_id_from_distinguished_name(
value, value,
&self.base_dn, &self.base_dn,
&self.base_dn_str, &self.base_dn_str,
)?; )?;
Ok(GroupRequestFilter::Member(user_name)) Ok(GroupRequestFilter::Member(user_name))
} else if field.to_lowercase() == "objectclass" { } else if field == "objectclass" {
if value == "groupOfUniqueNames" || value == "groupOfNames" { if value == "groupofuniquenames" || value == "groupofnames" {
Ok(GroupRequestFilter::And(vec![])) Ok(GroupRequestFilter::And(vec![]))
} else { } else {
Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And( Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
@ -725,7 +735,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
self.convert_group_filter(&*filter)?, self.convert_group_filter(&*filter)?,
))), ))),
LdapFilter::Present(field) => { LdapFilter::Present(field) => {
if ALL_GROUP_ATTRIBUTE_KEYS.contains(&field.to_lowercase().as_str()) { if ALL_GROUP_ATTRIBUTE_KEYS.contains(&field.to_ascii_lowercase().as_str()) {
Ok(GroupRequestFilter::And(vec![])) Ok(GroupRequestFilter::And(vec![]))
} else { } else {
Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And( Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
@ -755,14 +765,15 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
self.convert_user_filter(&*filter)?, self.convert_user_filter(&*filter)?,
))), ))),
LdapFilter::Equality(field, value) => { LdapFilter::Equality(field, value) => {
if field.to_lowercase() == "memberof" { let field = &field.to_ascii_lowercase();
if field == "memberof" {
let group_name = get_group_id_from_distinguished_name( let group_name = get_group_id_from_distinguished_name(
value, &value.to_ascii_lowercase(),
&self.base_dn, &self.base_dn,
&self.base_dn_str, &self.base_dn_str,
)?; )?;
Ok(UserRequestFilter::MemberOf(group_name)) Ok(UserRequestFilter::MemberOf(group_name))
} else if field.to_lowercase() == "objectclass" { } else if field == "objectclass" {
if value == "person" if value == "person"
|| value == "inetOrgPerson" || value == "inetOrgPerson"
|| value == "posixAccount" || value == "posixAccount"
@ -784,8 +795,9 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
} }
} }
LdapFilter::Present(field) => { LdapFilter::Present(field) => {
let field = &field.to_ascii_lowercase();
// Check that it's a field we support. // Check that it's a field we support.
if field.to_lowercase() == "objectclass" || map_field(field).is_ok() { if field == "objectclass" || map_field(field).is_ok() {
Ok(UserRequestFilter::And(vec![])) Ok(UserRequestFilter::And(vec![]))
} else { } else {
Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And( Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
@ -872,7 +884,7 @@ mod tests {
filter: LdapFilter, filter: LdapFilter,
attrs: Vec<S>, attrs: Vec<S>,
) -> LdapSearchRequest { ) -> LdapSearchRequest {
make_search_request::<S>("ou=people,dc=example,dc=com", filter, attrs) make_search_request::<S>("ou=people,Dc=example,dc=com", filter, attrs)
} }
async fn setup_bound_handler( async fn setup_bound_handler(
@ -891,9 +903,9 @@ mod tests {
set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string())); set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string()));
Ok(set) Ok(set)
}); });
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string()); let mut ldap_handler = LdapHandler::new(mock, "dc=Example,dc=com".to_string());
let request = LdapBindRequest { let request = LdapBindRequest {
dn: "uid=test,ou=people,dc=example,dc=com".to_string(), dn: "uid=test,ou=people,dc=example,dc=coM".to_string(),
cred: LdapBindCred::Simple("pass".to_string()), cred: LdapBindCred::Simple("pass".to_string()),
}; };
assert_eq!( assert_eq!(
@ -916,7 +928,7 @@ mod tests {
mock.expect_get_user_groups() mock.expect_get_user_groups()
.with(eq(UserId::new("bob"))) .with(eq(UserId::new("bob")))
.return_once(|_| Ok(HashSet::new())); .return_once(|_| Ok(HashSet::new()));
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string()); let mut ldap_handler = LdapHandler::new(mock, "dc=eXample,dc=com".to_string());
let request = LdapOp::BindRequest(LdapBindRequest { let request = LdapOp::BindRequest(LdapBindRequest {
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(), dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
@ -1240,14 +1252,14 @@ mod tests {
}, },
Group { Group {
id: GroupId(3), id: GroupId(3),
display_name: "bestgroup".to_string(), display_name: "BestGroup".to_string(),
users: vec![UserId::new("john")], users: vec![UserId::new("john")],
}, },
]) ])
}); });
let mut ldap_handler = setup_bound_handler(mock).await; let mut ldap_handler = setup_bound_handler(mock).await;
let request = make_search_request( let request = make_search_request(
"ou=groups,dc=example,dc=com", "ou=groups,dc=example,dc=cOm",
LdapFilter::And(vec![]), LdapFilter::And(vec![]),
vec!["objectClass", "dn", "cn", "uniqueMember"], vec!["objectClass", "dn", "cn", "uniqueMember"],
); );
@ -1279,7 +1291,7 @@ mod tests {
], ],
}), }),
LdapOp::SearchResultEntry(LdapSearchResultEntry { LdapOp::SearchResultEntry(LdapSearchResultEntry {
dn: "cn=bestgroup,ou=groups,dc=example,dc=com".to_string(), dn: "cn=BestGroup,ou=groups,dc=example,dc=com".to_string(),
attributes: vec![ attributes: vec![
LdapPartialAttribute { LdapPartialAttribute {
atype: "objectClass".to_string(), atype: "objectClass".to_string(),
@ -1287,11 +1299,11 @@ mod tests {
}, },
LdapPartialAttribute { LdapPartialAttribute {
atype: "dn".to_string(), atype: "dn".to_string(),
vals: vec!["cn=bestgroup,ou=groups,dc=example,dc=com".to_string()] vals: vec!["cn=BestGroup,ou=groups,dc=example,dc=com".to_string()]
}, },
LdapPartialAttribute { LdapPartialAttribute {
atype: "cn".to_string(), atype: "cn".to_string(),
vals: vec!["bestgroup".to_string()] vals: vec!["BestGroup".to_string()]
}, },
LdapPartialAttribute { LdapPartialAttribute {
atype: "uniqueMember".to_string(), atype: "uniqueMember".to_string(),
@ -1331,17 +1343,17 @@ mod tests {
let request = make_search_request( let request = make_search_request(
"ou=groups,dc=example,dc=com", "ou=groups,dc=example,dc=com",
LdapFilter::And(vec![ LdapFilter::And(vec![
LdapFilter::Equality("cn".to_string(), "group_1".to_string()), LdapFilter::Equality("cN".to_string(), "Group_1".to_string()),
LdapFilter::Equality( LdapFilter::Equality(
"uniqueMember".to_string(), "uniqueMember".to_string(),
"uid=bob,ou=people,dc=example,dc=com".to_string(), "uid=bob,ou=peopLe,Dc=eXample,dc=com".to_string(),
), ),
LdapFilter::Equality("objectclass".to_string(), "groupOfUniqueNames".to_string()), LdapFilter::Equality("obJEctclass".to_string(), "groupOfUniqueNames".to_string()),
LdapFilter::Equality("objectclass".to_string(), "groupOfNames".to_string()), LdapFilter::Equality("objectclass".to_string(), "groupOfNames".to_string()),
LdapFilter::Present("objectclass".to_string()), LdapFilter::Present("objectclass".to_string()),
LdapFilter::Present("dn".to_string()), LdapFilter::Present("dn".to_string()),
LdapFilter::Not(Box::new(LdapFilter::Present( LdapFilter::Not(Box::new(LdapFilter::Present(
"random_attribute".to_string(), "random_attribUte".to_string(),
))), ))),
]), ]),
vec!["1.1"], vec!["1.1"],