2021-10-28 16:04:09 +00:00
|
|
|
use crate::domain::{
|
|
|
|
handler::{
|
2022-03-26 17:00:37 +00:00
|
|
|
BackendHandler, BindRequest, Group, GroupRequestFilter, LoginHandler, User, UserId,
|
2022-02-12 10:00:02 +00:00
|
|
|
UserRequestFilter,
|
2021-10-28 16:04:09 +00:00
|
|
|
},
|
|
|
|
opaque_handler::OpaqueHandler,
|
2021-10-23 16:01:35 +00:00
|
|
|
};
|
2021-11-08 09:13:48 +00:00
|
|
|
use anyhow::{bail, Context, Result};
|
2022-05-07 04:48:53 +00:00
|
|
|
use itertools::Itertools;
|
2021-10-24 09:03:09 +00:00
|
|
|
use ldap3_server::proto::{
|
2021-10-28 16:04:09 +00:00
|
|
|
LdapBindCred, LdapBindRequest, LdapBindResponse, LdapExtendedRequest, LdapExtendedResponse,
|
|
|
|
LdapFilter, LdapOp, LdapPartialAttribute, LdapPasswordModifyRequest, LdapResult,
|
|
|
|
LdapResultCode, LdapSearchRequest, LdapSearchResultEntry, LdapSearchScope,
|
2021-10-24 09:03:09 +00:00
|
|
|
};
|
2021-11-07 13:57:34 +00:00
|
|
|
use log::{debug, warn};
|
2021-03-11 09:50:15 +00:00
|
|
|
|
2022-04-25 07:03:55 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
|
|
struct LdapDn(String);
|
|
|
|
|
2021-03-22 08:59:58 +00:00
|
|
|
fn make_dn_pair<I>(mut iter: I) -> Result<(String, String)>
|
2021-03-16 17:27:31 +00:00
|
|
|
where
|
|
|
|
I: Iterator<Item = String>,
|
|
|
|
{
|
|
|
|
let pair = (
|
|
|
|
iter.next()
|
2021-03-22 08:59:58 +00:00
|
|
|
.ok_or_else(|| anyhow::Error::msg("Empty DN element"))?,
|
2021-03-16 17:27:31 +00:00
|
|
|
iter.next()
|
2021-03-22 08:59:58 +00:00
|
|
|
.ok_or_else(|| anyhow::Error::msg("Missing DN value"))?,
|
2021-03-16 17:27:31 +00:00
|
|
|
);
|
|
|
|
if let Some(e) = iter.next() {
|
|
|
|
bail!(
|
|
|
|
r#"Too many elements in distinguished name: "{:?}", "{:?}", "{:?}""#,
|
|
|
|
pair.0,
|
|
|
|
pair.1,
|
|
|
|
e
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Ok(pair)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_distinguished_name(dn: &str) -> Result<Vec<(String, String)>> {
|
2022-05-30 15:59:02 +00:00
|
|
|
assert!(dn == dn.to_ascii_lowercase());
|
2021-03-22 08:59:58 +00:00
|
|
|
dn.split(',')
|
2022-02-11 07:40:10 +00:00
|
|
|
.map(|s| make_dn_pair(s.split('=').map(str::trim).map(String::from)))
|
2021-03-16 17:27:31 +00:00
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2021-09-24 20:27:07 +00:00
|
|
|
fn get_group_id_from_distinguished_name(
|
|
|
|
dn: &str,
|
|
|
|
base_tree: &[(String, String)],
|
|
|
|
base_dn_str: &str,
|
|
|
|
) -> Result<String> {
|
2021-11-08 09:13:48 +00:00
|
|
|
let parts = parse_distinguished_name(dn).context("while parsing a group ID")?;
|
2021-09-24 20:27:07 +00:00
|
|
|
if !is_subtree(&parts, base_tree) {
|
|
|
|
bail!("Not a subtree of the base tree");
|
|
|
|
}
|
|
|
|
if parts.len() == base_tree.len() + 2 {
|
|
|
|
if parts[1].0 != "ou" || parts[1].1 != "groups" || parts[0].0 != "cn" {
|
|
|
|
bail!(
|
2021-10-23 16:01:35 +00:00
|
|
|
r#"Unexpected group DN format. Got "{}", expected: "cn=groupname,ou=groups,{}""#,
|
|
|
|
dn,
|
2021-09-24 20:27:07 +00:00
|
|
|
base_dn_str
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Ok(parts[0].1.to_string())
|
|
|
|
} else {
|
|
|
|
bail!(
|
2021-10-23 16:01:35 +00:00
|
|
|
r#"Unexpected group DN format. Got "{}", expected: "cn=groupname,ou=groups,{}""#,
|
|
|
|
dn,
|
2021-09-24 20:27:07 +00:00
|
|
|
base_dn_str
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-13 17:31:37 +00:00
|
|
|
fn get_user_id_from_distinguished_name(
|
|
|
|
dn: &str,
|
|
|
|
base_tree: &[(String, String)],
|
|
|
|
base_dn_str: &str,
|
2022-03-26 17:00:37 +00:00
|
|
|
) -> Result<UserId> {
|
2021-11-08 09:13:48 +00:00
|
|
|
let parts = parse_distinguished_name(dn).context("while parsing a user ID")?;
|
2021-05-13 17:31:37 +00:00
|
|
|
if !is_subtree(&parts, base_tree) {
|
|
|
|
bail!("Not a subtree of the base tree");
|
|
|
|
}
|
2021-10-23 16:01:35 +00:00
|
|
|
if parts.len() == base_tree.len() + 2 {
|
2022-04-29 07:46:08 +00:00
|
|
|
if parts[1].0 != "ou"
|
|
|
|
|| parts[1].1 != "people"
|
|
|
|
|| (parts[0].0 != "cn" && parts[0].0 != "uid")
|
|
|
|
{
|
2021-05-13 17:31:37 +00:00
|
|
|
bail!(
|
2022-04-29 07:46:46 +00:00
|
|
|
r#"Unexpected user DN format. Got "{}", expected: "uid=username,ou=people,{}""#,
|
2021-10-23 16:01:35 +00:00
|
|
|
dn,
|
2021-05-13 17:31:37 +00:00
|
|
|
base_dn_str
|
|
|
|
);
|
|
|
|
}
|
2022-03-26 17:00:37 +00:00
|
|
|
Ok(UserId::new(&parts[0].1))
|
2021-05-13 17:31:37 +00:00
|
|
|
} else {
|
|
|
|
bail!(
|
2022-04-29 07:46:46 +00:00
|
|
|
r#"Unexpected user DN format. Got "{}", expected: "uid=username,ou=people,{}""#,
|
2021-10-23 16:01:35 +00:00
|
|
|
dn,
|
2021-05-13 17:31:37 +00:00
|
|
|
base_dn_str
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-25 07:03:55 +00:00
|
|
|
fn get_user_attribute(user: &User, attribute: &str, dn: &str) -> Result<Option<Vec<String>>> {
|
2022-05-30 15:59:02 +00:00
|
|
|
Ok(Some(match attribute.to_ascii_lowercase().as_str() {
|
2022-04-25 07:03:55 +00:00
|
|
|
"objectclass" => vec![
|
2021-03-22 08:59:58 +00:00
|
|
|
"inetOrgPerson".to_string(),
|
|
|
|
"posixAccount".to_string(),
|
|
|
|
"mailAccount".to_string(),
|
2021-10-23 16:01:35 +00:00
|
|
|
"person".to_string(),
|
2022-04-25 07:03:55 +00:00
|
|
|
],
|
2022-05-07 04:48:25 +00:00
|
|
|
"dn" | "distinguishedname" => vec![dn.to_string()],
|
2022-04-25 07:03:55 +00:00
|
|
|
"uid" => vec![user.user_id.to_string()],
|
|
|
|
"mail" => vec![user.email.clone()],
|
|
|
|
"givenname" => vec![user.first_name.clone()],
|
|
|
|
"sn" => vec![user.last_name.clone()],
|
|
|
|
"cn" | "displayname" => vec![user.display_name.clone()],
|
|
|
|
"createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()],
|
|
|
|
"1.1" => return Ok(None),
|
2022-05-07 04:48:53 +00:00
|
|
|
// We ignore the operational attribute wildcard
|
|
|
|
"+" => return Ok(None),
|
|
|
|
"*" => {
|
2022-05-12 10:46:46 +00:00
|
|
|
bail!(
|
2022-05-07 04:48:53 +00:00
|
|
|
"Matched {}, * should have been expanded into attribute list and * removed",
|
|
|
|
attribute
|
2022-05-12 10:46:46 +00:00
|
|
|
)
|
2022-05-07 04:48:53 +00:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
warn!("Ignoring unrecognized group attribute: {}", attribute);
|
|
|
|
return Ok(None);
|
|
|
|
}
|
2022-04-25 07:03:55 +00:00
|
|
|
}))
|
2021-03-16 17:27:31 +00:00
|
|
|
}
|
|
|
|
|
2022-05-07 04:48:53 +00:00
|
|
|
fn expand_attribute_wildcards(attributes: &[String], all_attribute_keys: &[&str]) -> Vec<String> {
|
|
|
|
let mut attributes_out = attributes.to_owned();
|
|
|
|
|
|
|
|
if attributes_out.iter().any(|x| x == "*") || attributes_out.is_empty() {
|
|
|
|
debug!(r#"Expanding * / empty attrs:"#);
|
|
|
|
// Remove occurrences of '*'
|
|
|
|
attributes_out.retain(|x| x != "*");
|
|
|
|
// Splice in all non-operational attributes
|
|
|
|
attributes_out.extend(all_attribute_keys.iter().map(|s| s.to_string()));
|
|
|
|
}
|
|
|
|
|
|
|
|
debug!(r#"Expanded: "{:?}""#, &attributes_out);
|
|
|
|
|
|
|
|
// Deduplicate, preserving order
|
|
|
|
attributes_out.into_iter().unique().collect_vec()
|
|
|
|
}
|
|
|
|
const ALL_USER_ATTRIBUTE_KEYS: &[&str] = &[
|
|
|
|
"objectclass",
|
|
|
|
"dn",
|
|
|
|
"uid",
|
|
|
|
"mail",
|
|
|
|
"givenname",
|
|
|
|
"sn",
|
|
|
|
"cn",
|
|
|
|
"createtimestamp",
|
|
|
|
];
|
|
|
|
|
2021-10-23 16:01:35 +00:00
|
|
|
fn make_ldap_search_user_result_entry(
|
2021-03-22 08:59:58 +00:00
|
|
|
user: User,
|
|
|
|
base_dn_str: &str,
|
|
|
|
attributes: &[String],
|
|
|
|
) -> Result<LdapSearchResultEntry> {
|
2022-05-02 13:29:52 +00:00
|
|
|
let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
|
2022-05-07 04:48:53 +00:00
|
|
|
|
|
|
|
let expanded_attributes = expand_attribute_wildcards(attributes, ALL_USER_ATTRIBUTE_KEYS);
|
2021-03-22 08:59:58 +00:00
|
|
|
Ok(LdapSearchResultEntry {
|
2021-10-28 14:24:01 +00:00
|
|
|
dn: dn.clone(),
|
2022-05-07 04:48:53 +00:00
|
|
|
attributes: expanded_attributes
|
2021-03-22 08:59:58 +00:00
|
|
|
.iter()
|
2022-04-25 07:03:55 +00:00
|
|
|
.filter_map(|a| {
|
|
|
|
let values = match get_user_attribute(&user, a, &dn) {
|
|
|
|
Err(e) => return Some(Err(e)),
|
|
|
|
Ok(v) => v,
|
|
|
|
}?;
|
|
|
|
Some(Ok(LdapPartialAttribute {
|
2021-03-22 08:59:58 +00:00
|
|
|
atype: a.to_string(),
|
2022-04-25 07:03:55 +00:00
|
|
|
vals: values,
|
|
|
|
}))
|
2021-10-23 16:01:35 +00:00
|
|
|
})
|
|
|
|
.collect::<Result<Vec<LdapPartialAttribute>>>()?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-25 07:03:55 +00:00
|
|
|
fn get_group_attribute(
|
|
|
|
group: &Group,
|
|
|
|
base_dn_str: &str,
|
|
|
|
attribute: &str,
|
|
|
|
user_filter: &Option<&UserId>,
|
|
|
|
) -> Result<Option<Vec<String>>> {
|
2022-05-30 15:59:02 +00:00
|
|
|
Ok(Some(match attribute.to_ascii_lowercase().as_str() {
|
2022-04-25 07:03:55 +00:00
|
|
|
"objectclass" => vec!["groupOfUniqueNames".to_string()],
|
2022-05-07 04:48:25 +00:00
|
|
|
"dn" | "distinguishedname" => vec![format!(
|
2021-10-28 16:04:09 +00:00
|
|
|
"cn={},ou=groups,{}",
|
|
|
|
group.display_name, base_dn_str
|
2022-04-25 07:03:55 +00:00
|
|
|
)],
|
|
|
|
"cn" | "uid" => vec![group.display_name.clone()],
|
|
|
|
"member" | "uniquemember" => group
|
2021-10-23 16:01:35 +00:00
|
|
|
.users
|
|
|
|
.iter()
|
2022-04-25 07:03:55 +00:00
|
|
|
.filter(|u| user_filter.map(|f| *u == f).unwrap_or(true))
|
2022-05-02 13:29:52 +00:00
|
|
|
.map(|u| format!("uid={},ou=people,{}", u, base_dn_str))
|
2022-04-25 07:03:55 +00:00
|
|
|
.collect(),
|
|
|
|
"1.1" => return Ok(None),
|
2022-05-07 04:48:53 +00:00
|
|
|
// We ignore the operational attribute wildcard
|
|
|
|
"+" => return Ok(None),
|
|
|
|
"*" => {
|
2022-05-12 10:46:46 +00:00
|
|
|
bail!(
|
2022-05-07 04:48:53 +00:00
|
|
|
"Matched {}, * should have been expanded into attribute list and * removed",
|
|
|
|
attribute
|
2022-05-12 10:46:46 +00:00
|
|
|
)
|
2022-05-07 04:48:53 +00:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
warn!("Ignoring unrecognized group attribute: {}", attribute);
|
|
|
|
return Ok(None);
|
|
|
|
}
|
2022-04-25 07:03:55 +00:00
|
|
|
}))
|
2021-10-23 16:01:35 +00:00
|
|
|
}
|
|
|
|
|
2022-05-07 04:48:53 +00:00
|
|
|
const ALL_GROUP_ATTRIBUTE_KEYS: &[&str] =
|
|
|
|
&["objectclass", "dn", "uid", "cn", "member", "uniquemember"];
|
|
|
|
|
2021-10-23 16:01:35 +00:00
|
|
|
fn make_ldap_search_group_result_entry(
|
|
|
|
group: Group,
|
|
|
|
base_dn_str: &str,
|
|
|
|
attributes: &[String],
|
2022-04-25 07:03:55 +00:00
|
|
|
user_filter: &Option<&UserId>,
|
2021-10-23 16:01:35 +00:00
|
|
|
) -> Result<LdapSearchResultEntry> {
|
2022-05-07 04:48:53 +00:00
|
|
|
let expanded_attributes = expand_attribute_wildcards(attributes, ALL_GROUP_ATTRIBUTE_KEYS);
|
|
|
|
|
2021-10-23 16:01:35 +00:00
|
|
|
Ok(LdapSearchResultEntry {
|
|
|
|
dn: format!("cn={},ou=groups,{}", group.display_name, base_dn_str),
|
2022-05-07 04:48:53 +00:00
|
|
|
attributes: expanded_attributes
|
2021-10-23 16:01:35 +00:00
|
|
|
.iter()
|
2022-04-25 07:03:55 +00:00
|
|
|
.filter_map(|a| {
|
|
|
|
let values = match get_group_attribute(&group, base_dn_str, a, user_filter) {
|
|
|
|
Err(e) => return Some(Err(e)),
|
|
|
|
Ok(v) => v,
|
|
|
|
}?;
|
|
|
|
Some(Ok(LdapPartialAttribute {
|
2021-10-23 16:01:35 +00:00
|
|
|
atype: a.to_string(),
|
2022-04-25 07:03:55 +00:00
|
|
|
vals: values,
|
|
|
|
}))
|
2021-03-22 08:59:58 +00:00
|
|
|
})
|
|
|
|
.collect::<Result<Vec<LdapPartialAttribute>>>()?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)]) -> bool {
|
2022-05-30 15:59:02 +00:00
|
|
|
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());
|
|
|
|
}
|
2021-03-16 17:27:31 +00:00
|
|
|
if subtree.len() < base_tree.len() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let size_diff = subtree.len() - base_tree.len();
|
|
|
|
for i in 0..base_tree.len() {
|
|
|
|
if subtree[size_diff + i] != base_tree[i] {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2021-04-14 19:22:08 +00:00
|
|
|
fn map_field(field: &str) -> Result<String> {
|
2022-05-30 15:59:02 +00:00
|
|
|
assert!(field == field.to_ascii_lowercase());
|
2021-04-14 19:22:08 +00:00
|
|
|
Ok(if field == "uid" {
|
|
|
|
"user_id".to_string()
|
|
|
|
} else if field == "mail" {
|
|
|
|
"email".to_string()
|
2022-05-30 15:59:02 +00:00
|
|
|
} else if field == "cn" || field == "displayname" {
|
2021-04-14 19:22:08 +00:00
|
|
|
"display_name".to_string()
|
2022-05-30 15:59:02 +00:00
|
|
|
} else if field == "givenname" {
|
2021-04-14 19:22:08 +00:00
|
|
|
"first_name".to_string()
|
|
|
|
} else if field == "sn" {
|
|
|
|
"last_name".to_string()
|
|
|
|
} else if field == "avatar" {
|
|
|
|
"avatar".to_string()
|
2022-05-30 15:59:02 +00:00
|
|
|
} else if field == "creationdate" || field == "createtimestamp" || field == "modifytimestamp" {
|
2021-04-14 19:22:08 +00:00
|
|
|
"creation_date".to_string()
|
|
|
|
} else {
|
|
|
|
bail!("Unknown field: {}", field);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-24 09:03:09 +00:00
|
|
|
fn make_search_success() -> LdapOp {
|
|
|
|
make_search_error(LdapResultCode::Success, "".to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_search_error(code: LdapResultCode, message: String) -> LdapOp {
|
|
|
|
LdapOp::SearchResultDone(LdapResult {
|
|
|
|
code,
|
|
|
|
matcheddn: "".to_string(),
|
|
|
|
message,
|
|
|
|
referral: vec![],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-28 16:04:09 +00:00
|
|
|
fn make_extended_response(code: LdapResultCode, message: String) -> LdapOp {
|
|
|
|
LdapOp::ExtendedResponse(LdapExtendedResponse {
|
|
|
|
res: LdapResult {
|
|
|
|
code,
|
|
|
|
matcheddn: "".to_string(),
|
|
|
|
message,
|
|
|
|
referral: vec![],
|
|
|
|
},
|
|
|
|
name: None,
|
|
|
|
value: None,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-28 16:03:26 +00:00
|
|
|
fn root_dse_response(base_dn: &str) -> LdapOp {
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
|
|
|
dn: "".to_string(),
|
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectClass".to_string(),
|
|
|
|
vals: vec!["top".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "vendorName".to_string(),
|
|
|
|
vals: vec!["LLDAP".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "vendorVersion".to_string(),
|
|
|
|
vals: vec!["lldap_0.2.0".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "supportedLDAPVersion".to_string(),
|
|
|
|
vals: vec!["3".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "supportedExtension".to_string(),
|
|
|
|
vals: vec!["1.3.6.1.4.1.4203.1.11.1".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "defaultnamingcontext".to_string(),
|
|
|
|
vals: vec![base_dn.to_string()],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-05-08 18:12:06 +00:00
|
|
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
|
|
enum Permission {
|
|
|
|
Admin,
|
|
|
|
Regular,
|
|
|
|
}
|
|
|
|
|
2021-10-28 16:04:09 +00:00
|
|
|
pub struct LdapHandler<Backend: BackendHandler + LoginHandler + OpaqueHandler> {
|
2022-05-08 18:12:06 +00:00
|
|
|
user_info: Option<(UserId, Permission)>,
|
2021-03-11 09:50:15 +00:00
|
|
|
backend_handler: Backend,
|
2021-03-16 17:27:31 +00:00
|
|
|
pub base_dn: Vec<(String, String)>,
|
|
|
|
base_dn_str: String,
|
2021-03-11 09:50:15 +00:00
|
|
|
}
|
|
|
|
|
2021-10-28 16:04:09 +00:00
|
|
|
impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend> {
|
2022-05-30 15:59:02 +00:00
|
|
|
pub fn new(backend_handler: Backend, mut ldap_base_dn: String) -> Self {
|
|
|
|
ldap_base_dn.make_ascii_lowercase();
|
2021-03-11 09:50:15 +00:00
|
|
|
Self {
|
2022-05-08 18:12:06 +00:00
|
|
|
user_info: None,
|
2021-03-11 09:50:15 +00:00
|
|
|
backend_handler,
|
2021-03-22 08:59:58 +00:00
|
|
|
base_dn: parse_distinguished_name(&ldap_base_dn).unwrap_or_else(|_| {
|
|
|
|
panic!(
|
|
|
|
"Invalid value for ldap_base_dn in configuration: {}",
|
|
|
|
ldap_base_dn
|
|
|
|
)
|
|
|
|
}),
|
2021-03-16 17:27:31 +00:00
|
|
|
base_dn_str: ldap_base_dn,
|
2021-03-11 09:50:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-24 09:03:09 +00:00
|
|
|
pub async fn do_bind(&mut self, request: &LdapBindRequest) -> (LdapResultCode, String) {
|
2021-11-07 13:57:34 +00:00
|
|
|
debug!(r#"Received bind request for "{}""#, &request.dn);
|
2021-10-24 09:03:09 +00:00
|
|
|
let user_id = match get_user_id_from_distinguished_name(
|
2022-05-30 15:59:02 +00:00
|
|
|
&request.dn.to_ascii_lowercase(),
|
2021-10-24 09:03:09 +00:00
|
|
|
&self.base_dn,
|
|
|
|
&self.base_dn_str,
|
|
|
|
) {
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(e) => return (LdapResultCode::NamingViolation, e.to_string()),
|
|
|
|
};
|
|
|
|
let LdapBindCred::Simple(password) = &request.cred;
|
2021-03-11 09:50:15 +00:00
|
|
|
match self
|
|
|
|
.backend_handler
|
2021-10-24 09:03:09 +00:00
|
|
|
.bind(BindRequest {
|
2022-04-25 07:03:55 +00:00
|
|
|
name: user_id.clone(),
|
2021-10-24 09:03:09 +00:00
|
|
|
password: password.clone(),
|
2021-04-07 18:14:21 +00:00
|
|
|
})
|
|
|
|
.await
|
|
|
|
{
|
2021-03-11 09:50:15 +00:00
|
|
|
Ok(()) => {
|
2022-05-08 18:12:06 +00:00
|
|
|
let is_admin = self
|
|
|
|
.backend_handler
|
|
|
|
.get_user_groups(&user_id)
|
|
|
|
.await
|
|
|
|
.map(|groups| groups.iter().any(|g| g.1 == "lldap_admin"))
|
|
|
|
.unwrap_or(false);
|
|
|
|
self.user_info = Some((
|
|
|
|
user_id,
|
|
|
|
if is_admin {
|
|
|
|
Permission::Admin
|
|
|
|
} else {
|
|
|
|
Permission::Regular
|
|
|
|
},
|
|
|
|
));
|
2021-10-24 09:03:09 +00:00
|
|
|
(LdapResultCode::Success, "".to_string())
|
2021-03-11 09:50:15 +00:00
|
|
|
}
|
2021-10-24 09:03:09 +00:00
|
|
|
Err(_) => (LdapResultCode::InvalidCredentials, "".to_string()),
|
2021-03-11 09:50:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-26 17:00:37 +00:00
|
|
|
async fn change_password(&mut self, user: &UserId, password: &str) -> Result<()> {
|
2021-10-28 16:04:09 +00:00
|
|
|
use lldap_auth::*;
|
|
|
|
let mut rng = rand::rngs::OsRng;
|
|
|
|
let registration_start_request =
|
|
|
|
opaque::client::registration::start_registration(password, &mut rng)?;
|
|
|
|
let req = registration::ClientRegistrationStartRequest {
|
|
|
|
username: user.to_string(),
|
|
|
|
registration_start_request: registration_start_request.message,
|
|
|
|
};
|
|
|
|
let registration_start_response = self.backend_handler.registration_start(req).await?;
|
|
|
|
let registration_finish = opaque::client::registration::finish_registration(
|
|
|
|
registration_start_request.state,
|
|
|
|
registration_start_response.registration_response,
|
|
|
|
&mut rng,
|
|
|
|
)?;
|
|
|
|
let req = registration::ClientRegistrationFinishRequest {
|
|
|
|
server_data: registration_start_response.server_data,
|
|
|
|
registration_upload: registration_finish.message,
|
|
|
|
};
|
|
|
|
self.backend_handler.registration_finish(req).await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn do_password_modification(
|
|
|
|
&mut self,
|
|
|
|
request: &LdapPasswordModifyRequest,
|
|
|
|
) -> Vec<LdapOp> {
|
2022-05-08 18:12:06 +00:00
|
|
|
let (user_id, permission) = match &self.user_info {
|
|
|
|
Some(info) => info,
|
|
|
|
_ => {
|
|
|
|
return vec![make_search_error(
|
|
|
|
LdapResultCode::InsufficentAccessRights,
|
|
|
|
"No user currently bound".to_string(),
|
|
|
|
)];
|
|
|
|
}
|
|
|
|
};
|
2021-10-28 16:04:09 +00:00
|
|
|
match (&request.user_identity, &request.new_password) {
|
|
|
|
(Some(user), Some(password)) => {
|
|
|
|
match get_user_id_from_distinguished_name(user, &self.base_dn, &self.base_dn_str) {
|
|
|
|
Ok(uid) => {
|
2022-05-08 18:12:06 +00:00
|
|
|
if *permission != Permission::Admin && user_id != &uid {
|
|
|
|
return vec![make_search_error(
|
|
|
|
LdapResultCode::InsufficentAccessRights,
|
|
|
|
format!(
|
|
|
|
r#"User {} cannot modify the password of user {}"#,
|
|
|
|
&user_id, &uid
|
|
|
|
),
|
|
|
|
)];
|
|
|
|
}
|
2021-10-28 16:04:09 +00:00
|
|
|
if let Err(e) = self.change_password(&uid, password).await {
|
|
|
|
vec![make_extended_response(
|
|
|
|
LdapResultCode::Other,
|
|
|
|
format!("Error while changing the password: {:#?}", e),
|
|
|
|
)]
|
|
|
|
} else {
|
|
|
|
vec![make_extended_response(
|
|
|
|
LdapResultCode::Success,
|
|
|
|
"".to_string(),
|
|
|
|
)]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => vec![make_extended_response(
|
|
|
|
LdapResultCode::InvalidDNSyntax,
|
2021-11-08 09:49:25 +00:00
|
|
|
format!("Invalid username: {:#?}", e),
|
2021-10-28 16:04:09 +00:00
|
|
|
)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => vec![make_extended_response(
|
|
|
|
LdapResultCode::ConstraintViolation,
|
|
|
|
"Missing either user_id or password".to_string(),
|
|
|
|
)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn do_extended_request(&mut self, request: &LdapExtendedRequest) -> Vec<LdapOp> {
|
|
|
|
match LdapPasswordModifyRequest::try_from(request) {
|
|
|
|
Ok(password_request) => self.do_password_modification(&password_request).await,
|
|
|
|
Err(_) => vec![make_extended_response(
|
|
|
|
LdapResultCode::UnwillingToPerform,
|
|
|
|
format!("Unsupported extended operation: {}", &request.name),
|
|
|
|
)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-24 09:03:09 +00:00
|
|
|
pub async fn do_search(&mut self, request: &LdapSearchRequest) -> Vec<LdapOp> {
|
2022-05-08 18:12:06 +00:00
|
|
|
let user_filter = match &self.user_info {
|
|
|
|
Some((_, Permission::Admin)) => None,
|
|
|
|
Some((user_id, Permission::Regular)) => Some(user_id),
|
|
|
|
None => {
|
|
|
|
return vec![make_search_error(
|
|
|
|
LdapResultCode::InsufficentAccessRights,
|
|
|
|
"No user currently bound".to_string(),
|
|
|
|
)];
|
|
|
|
}
|
|
|
|
};
|
2021-10-28 16:03:26 +00:00
|
|
|
if request.base.is_empty()
|
|
|
|
&& request.scope == LdapSearchScope::Base
|
|
|
|
&& request.filter == LdapFilter::Present("objectClass".to_string())
|
|
|
|
{
|
2021-11-07 13:57:34 +00:00
|
|
|
debug!("Received rootDSE request");
|
2021-10-28 16:03:26 +00:00
|
|
|
return vec![root_dse_response(&self.base_dn_str), make_search_success()];
|
|
|
|
}
|
2021-11-07 13:57:34 +00:00
|
|
|
debug!("Received search request: {:?}", &request);
|
2022-05-30 15:59:02 +00:00
|
|
|
let dn_parts = match parse_distinguished_name(&request.base.to_ascii_lowercase()) {
|
2021-10-28 16:03:26 +00:00
|
|
|
Ok(dn) => dn,
|
|
|
|
Err(_) => {
|
|
|
|
return vec![make_search_error(
|
|
|
|
LdapResultCode::OperationsError,
|
|
|
|
format!(r#"Could not parse base DN: "{}""#, request.base),
|
|
|
|
)]
|
2021-03-16 17:27:31 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
if !is_subtree(&dn_parts, &self.base_dn) {
|
2021-03-22 08:59:58 +00:00
|
|
|
// Search path is not in our tree, just return an empty success.
|
2021-11-07 13:57:34 +00:00
|
|
|
warn!(
|
|
|
|
"The specified search tree {:?} is not under the common subtree {:?}",
|
|
|
|
&dn_parts, &self.base_dn
|
|
|
|
);
|
2021-10-24 09:03:09 +00:00
|
|
|
return vec![make_search_success()];
|
2021-03-16 17:27:31 +00:00
|
|
|
}
|
2021-10-23 16:01:35 +00:00
|
|
|
let mut results = Vec::new();
|
2021-11-07 13:57:13 +00:00
|
|
|
let mut got_match = false;
|
2021-10-23 16:01:35 +00:00
|
|
|
if dn_parts.len() == self.base_dn.len()
|
|
|
|
|| (dn_parts.len() == self.base_dn.len() + 1
|
|
|
|
&& dn_parts[0] == ("ou".to_string(), "people".to_string()))
|
|
|
|
{
|
2021-11-07 13:57:13 +00:00
|
|
|
got_match = true;
|
2022-04-25 07:03:55 +00:00
|
|
|
results.extend(self.get_user_list(request, &user_filter).await);
|
2021-10-23 16:01:35 +00:00
|
|
|
}
|
2021-11-07 13:57:13 +00:00
|
|
|
if dn_parts.len() == self.base_dn.len()
|
|
|
|
|| (dn_parts.len() == self.base_dn.len() + 1
|
|
|
|
&& dn_parts[0] == ("ou".to_string(), "groups".to_string()))
|
2021-10-23 16:01:35 +00:00
|
|
|
{
|
2021-11-07 13:57:13 +00:00
|
|
|
got_match = true;
|
2022-04-25 07:03:55 +00:00
|
|
|
results.extend(self.get_groups_list(request, &user_filter).await);
|
2021-10-23 16:01:35 +00:00
|
|
|
}
|
2021-11-07 13:57:13 +00:00
|
|
|
if !got_match {
|
|
|
|
warn!(
|
|
|
|
r#"The requested search tree "{}" matches neither the user subtree "ou=people,{}" nor the group subtree "ou=groups,{}""#,
|
|
|
|
&request.base, &self.base_dn_str, &self.base_dn_str
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if results.is_empty() || matches!(results[results.len() - 1], LdapOp::SearchResultEntry(_))
|
|
|
|
{
|
|
|
|
results.push(make_search_success());
|
|
|
|
}
|
2021-10-23 16:01:35 +00:00
|
|
|
results
|
|
|
|
}
|
|
|
|
|
2022-04-25 07:03:55 +00:00
|
|
|
async fn get_user_list(
|
|
|
|
&self,
|
|
|
|
request: &LdapSearchRequest,
|
|
|
|
user_filter: &Option<&UserId>,
|
|
|
|
) -> Vec<LdapOp> {
|
2021-10-24 09:03:09 +00:00
|
|
|
let filters = match self.convert_user_filter(&request.filter) {
|
2022-04-25 07:03:55 +00:00
|
|
|
Ok(f) => f,
|
2021-09-24 20:27:07 +00:00
|
|
|
Err(e) => {
|
2021-10-24 09:03:09 +00:00
|
|
|
return vec![make_search_error(
|
2021-04-07 18:55:23 +00:00
|
|
|
LdapResultCode::UnwillingToPerform,
|
2021-11-08 09:13:48 +00:00
|
|
|
format!("Unsupported user filter: {:#}", e),
|
2021-04-07 18:55:23 +00:00
|
|
|
)]
|
|
|
|
}
|
|
|
|
};
|
2022-04-25 07:03:55 +00:00
|
|
|
let filters = match user_filter {
|
|
|
|
None => filters,
|
|
|
|
Some(u) => {
|
|
|
|
UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let users = match self.backend_handler.list_users(Some(filters)).await {
|
2021-03-16 17:27:31 +00:00
|
|
|
Ok(users) => users,
|
|
|
|
Err(e) => {
|
2021-10-24 09:03:09 +00:00
|
|
|
return vec![make_search_error(
|
2021-03-16 17:27:31 +00:00
|
|
|
LdapResultCode::Other,
|
2021-11-08 09:13:48 +00:00
|
|
|
format!(r#"Error during searching user "{}": {:#}"#, request.base, e),
|
2021-03-16 17:27:31 +00:00
|
|
|
)]
|
|
|
|
}
|
|
|
|
};
|
2021-03-22 08:59:58 +00:00
|
|
|
|
|
|
|
users
|
2021-03-16 17:27:31 +00:00
|
|
|
.into_iter()
|
2021-10-24 09:03:09 +00:00
|
|
|
.map(|u| make_ldap_search_user_result_entry(u, &self.base_dn_str, &request.attrs))
|
|
|
|
.map(|entry| Ok(LdapOp::SearchResultEntry(entry?)))
|
2021-10-23 16:01:35 +00:00
|
|
|
.collect::<Result<Vec<_>>>()
|
2021-10-24 09:03:09 +00:00
|
|
|
.unwrap_or_else(|e| {
|
|
|
|
vec![make_search_error(
|
|
|
|
LdapResultCode::NoSuchAttribute,
|
|
|
|
e.to_string(),
|
|
|
|
)]
|
|
|
|
})
|
2021-10-23 16:01:35 +00:00
|
|
|
}
|
|
|
|
|
2022-04-25 07:03:55 +00:00
|
|
|
async fn get_groups_list(
|
|
|
|
&self,
|
|
|
|
request: &LdapSearchRequest,
|
|
|
|
user_filter: &Option<&UserId>,
|
|
|
|
) -> Vec<LdapOp> {
|
2022-02-12 10:00:02 +00:00
|
|
|
let filter = match self.convert_group_filter(&request.filter) {
|
|
|
|
Ok(f) => f,
|
2021-10-23 16:01:35 +00:00
|
|
|
Err(e) => {
|
2021-10-24 09:03:09 +00:00
|
|
|
return vec![make_search_error(
|
2021-10-23 16:01:35 +00:00
|
|
|
LdapResultCode::UnwillingToPerform,
|
2021-11-08 09:13:48 +00:00
|
|
|
format!("Unsupported group filter: {:#}", e),
|
2021-10-23 16:01:35 +00:00
|
|
|
)]
|
|
|
|
}
|
|
|
|
};
|
2022-04-25 07:03:55 +00:00
|
|
|
let filter = match user_filter {
|
|
|
|
None => filter,
|
|
|
|
Some(u) => {
|
|
|
|
GroupRequestFilter::And(vec![filter, GroupRequestFilter::Member((*u).clone())])
|
|
|
|
}
|
|
|
|
};
|
2021-10-23 16:01:35 +00:00
|
|
|
|
2022-02-12 10:00:02 +00:00
|
|
|
let groups = match self.backend_handler.list_groups(Some(filter)).await {
|
|
|
|
Ok(groups) => groups,
|
|
|
|
Err(e) => {
|
|
|
|
return vec![make_search_error(
|
|
|
|
LdapResultCode::Other,
|
|
|
|
format!(r#"Error while listing groups "{}": {:#}"#, request.base, e),
|
|
|
|
)]
|
2021-10-23 16:01:35 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
groups
|
|
|
|
.into_iter()
|
2022-04-25 07:03:55 +00:00
|
|
|
.map(|u| {
|
|
|
|
make_ldap_search_group_result_entry(
|
|
|
|
u,
|
|
|
|
&self.base_dn_str,
|
|
|
|
&request.attrs,
|
|
|
|
user_filter,
|
|
|
|
)
|
|
|
|
})
|
2021-10-24 09:03:09 +00:00
|
|
|
.map(|entry| Ok(LdapOp::SearchResultEntry(entry?)))
|
2021-03-22 08:59:58 +00:00
|
|
|
.collect::<Result<Vec<_>>>()
|
2021-10-24 09:03:09 +00:00
|
|
|
.unwrap_or_else(|e| {
|
|
|
|
vec![make_search_error(
|
|
|
|
LdapResultCode::NoSuchAttribute,
|
|
|
|
e.to_string(),
|
|
|
|
)]
|
|
|
|
})
|
2021-03-11 09:50:15 +00:00
|
|
|
}
|
|
|
|
|
2021-10-24 09:03:09 +00:00
|
|
|
pub async fn handle_ldap_message(&mut self, ldap_op: LdapOp) -> Option<Vec<LdapOp>> {
|
|
|
|
Some(match ldap_op {
|
|
|
|
LdapOp::BindRequest(request) => {
|
|
|
|
let (code, message) = self.do_bind(&request).await;
|
|
|
|
vec![LdapOp::BindResponse(LdapBindResponse {
|
|
|
|
res: LdapResult {
|
|
|
|
code,
|
|
|
|
matcheddn: "".to_string(),
|
|
|
|
message,
|
|
|
|
referral: vec![],
|
|
|
|
},
|
|
|
|
saslcreds: None,
|
|
|
|
})]
|
|
|
|
}
|
|
|
|
LdapOp::SearchRequest(request) => self.do_search(&request).await,
|
|
|
|
LdapOp::UnbindRequest => {
|
2022-05-08 18:12:06 +00:00
|
|
|
self.user_info = None;
|
2021-03-11 09:50:15 +00:00
|
|
|
// No need to notify on unbind (per rfc4511)
|
|
|
|
return None;
|
|
|
|
}
|
2021-10-28 16:04:09 +00:00
|
|
|
LdapOp::ExtendedRequest(request) => self.do_extended_request(&request).await,
|
|
|
|
op => vec![make_extended_response(
|
|
|
|
LdapResultCode::UnwillingToPerform,
|
|
|
|
format!("Unsupported operation: {:#?}", op),
|
|
|
|
)],
|
2021-10-23 16:01:35 +00:00
|
|
|
})
|
2021-03-11 09:50:15 +00:00
|
|
|
}
|
2021-09-24 20:27:07 +00:00
|
|
|
|
2022-02-12 10:00:02 +00:00
|
|
|
fn convert_group_filter(&self, filter: &LdapFilter) -> Result<GroupRequestFilter> {
|
2021-10-23 16:01:35 +00:00
|
|
|
match filter {
|
|
|
|
LdapFilter::Equality(field, value) => {
|
2022-05-30 15:59:02 +00:00
|
|
|
let field = &field.to_ascii_lowercase();
|
|
|
|
let value = &value.to_ascii_lowercase();
|
|
|
|
if field == "member" || field == "uniquemember" {
|
2021-10-23 16:01:35 +00:00
|
|
|
let user_name = get_user_id_from_distinguished_name(
|
|
|
|
value,
|
|
|
|
&self.base_dn,
|
|
|
|
&self.base_dn_str,
|
|
|
|
)?;
|
2022-02-12 10:00:02 +00:00
|
|
|
Ok(GroupRequestFilter::Member(user_name))
|
2022-05-30 15:59:02 +00:00
|
|
|
} else if field == "objectclass" {
|
|
|
|
if value == "groupofuniquenames" || value == "groupofnames" {
|
2022-04-19 15:58:40 +00:00
|
|
|
Ok(GroupRequestFilter::And(vec![]))
|
|
|
|
} else {
|
|
|
|
Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
|
|
|
|
vec![],
|
|
|
|
))))
|
|
|
|
}
|
2021-10-23 16:01:35 +00:00
|
|
|
} else {
|
2022-02-12 10:00:02 +00:00
|
|
|
let field = map_field(field)?;
|
|
|
|
if field == "display_name" {
|
|
|
|
Ok(GroupRequestFilter::DisplayName(value.clone()))
|
|
|
|
} else {
|
|
|
|
bail!("Unsupported group attribute: {:?}", field)
|
|
|
|
}
|
2021-10-23 16:01:35 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-12 10:00:02 +00:00
|
|
|
LdapFilter::And(filters) => Ok(GroupRequestFilter::And(
|
|
|
|
filters
|
|
|
|
.iter()
|
|
|
|
.map(|f| self.convert_group_filter(f))
|
|
|
|
.collect::<Result<_>>()?,
|
|
|
|
)),
|
|
|
|
LdapFilter::Or(filters) => Ok(GroupRequestFilter::Or(
|
|
|
|
filters
|
|
|
|
.iter()
|
|
|
|
.map(|f| self.convert_group_filter(f))
|
|
|
|
.collect::<Result<_>>()?,
|
|
|
|
)),
|
|
|
|
LdapFilter::Not(filter) => Ok(GroupRequestFilter::Not(Box::new(
|
|
|
|
self.convert_group_filter(&*filter)?,
|
|
|
|
))),
|
2022-05-29 15:41:37 +00:00
|
|
|
LdapFilter::Present(field) => {
|
2022-05-30 15:59:02 +00:00
|
|
|
if ALL_GROUP_ATTRIBUTE_KEYS.contains(&field.to_ascii_lowercase().as_str()) {
|
2022-05-29 15:41:37 +00:00
|
|
|
Ok(GroupRequestFilter::And(vec![]))
|
|
|
|
} else {
|
|
|
|
Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
|
|
|
|
vec![],
|
|
|
|
))))
|
|
|
|
}
|
|
|
|
}
|
2021-10-23 16:01:35 +00:00
|
|
|
_ => bail!("Unsupported group filter: {:?}", filter),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-11 08:10:28 +00:00
|
|
|
fn convert_user_filter(&self, filter: &LdapFilter) -> Result<UserRequestFilter> {
|
2021-09-24 20:27:07 +00:00
|
|
|
match filter {
|
2022-02-11 08:10:28 +00:00
|
|
|
LdapFilter::And(filters) => Ok(UserRequestFilter::And(
|
2021-09-24 20:27:07 +00:00
|
|
|
filters
|
|
|
|
.iter()
|
2021-10-23 16:01:35 +00:00
|
|
|
.map(|f| self.convert_user_filter(f))
|
2021-09-24 20:27:07 +00:00
|
|
|
.collect::<Result<_>>()?,
|
|
|
|
)),
|
2022-02-11 08:10:28 +00:00
|
|
|
LdapFilter::Or(filters) => Ok(UserRequestFilter::Or(
|
2021-09-24 20:27:07 +00:00
|
|
|
filters
|
|
|
|
.iter()
|
2021-10-23 16:01:35 +00:00
|
|
|
.map(|f| self.convert_user_filter(f))
|
2021-09-24 20:27:07 +00:00
|
|
|
.collect::<Result<_>>()?,
|
|
|
|
)),
|
2022-02-11 08:10:28 +00:00
|
|
|
LdapFilter::Not(filter) => Ok(UserRequestFilter::Not(Box::new(
|
2021-10-23 16:01:35 +00:00
|
|
|
self.convert_user_filter(&*filter)?,
|
|
|
|
))),
|
2021-09-24 20:27:07 +00:00
|
|
|
LdapFilter::Equality(field, value) => {
|
2022-05-30 15:59:02 +00:00
|
|
|
let field = &field.to_ascii_lowercase();
|
|
|
|
if field == "memberof" {
|
2021-09-24 20:27:07 +00:00
|
|
|
let group_name = get_group_id_from_distinguished_name(
|
2022-05-30 15:59:02 +00:00
|
|
|
&value.to_ascii_lowercase(),
|
2021-09-24 20:27:07 +00:00
|
|
|
&self.base_dn,
|
|
|
|
&self.base_dn_str,
|
|
|
|
)?;
|
2022-02-11 08:10:28 +00:00
|
|
|
Ok(UserRequestFilter::MemberOf(group_name))
|
2022-05-30 15:59:02 +00:00
|
|
|
} else if field == "objectclass" {
|
2021-10-23 16:01:35 +00:00
|
|
|
if value == "person"
|
|
|
|
|| value == "inetOrgPerson"
|
|
|
|
|| value == "posixAccount"
|
|
|
|
|| value == "mailAccount"
|
|
|
|
{
|
2022-02-11 08:10:28 +00:00
|
|
|
Ok(UserRequestFilter::And(vec![]))
|
2021-10-23 16:01:35 +00:00
|
|
|
} else {
|
2022-02-12 10:00:02 +00:00
|
|
|
Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
|
|
|
|
vec![],
|
|
|
|
))))
|
2021-10-23 16:01:35 +00:00
|
|
|
}
|
2021-09-24 20:27:07 +00:00
|
|
|
} else {
|
2022-03-26 17:00:37 +00:00
|
|
|
let field = map_field(field)?;
|
|
|
|
if field == "user_id" {
|
|
|
|
Ok(UserRequestFilter::UserId(UserId::new(value)))
|
|
|
|
} else {
|
|
|
|
Ok(UserRequestFilter::Equality(field, value.clone()))
|
|
|
|
}
|
2021-09-24 20:27:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
LdapFilter::Present(field) => {
|
2022-05-30 15:59:02 +00:00
|
|
|
let field = &field.to_ascii_lowercase();
|
2021-09-24 20:27:07 +00:00
|
|
|
// Check that it's a field we support.
|
2022-05-30 15:59:02 +00:00
|
|
|
if field == "objectclass" || map_field(field).is_ok() {
|
2022-02-11 08:10:28 +00:00
|
|
|
Ok(UserRequestFilter::And(vec![]))
|
2021-09-24 20:27:07 +00:00
|
|
|
} else {
|
2022-02-12 10:00:02 +00:00
|
|
|
Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
|
|
|
|
vec![],
|
|
|
|
))))
|
2021-09-24 20:27:07 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-23 16:01:35 +00:00
|
|
|
_ => bail!("Unsupported user filter: {:?}", filter),
|
2021-09-24 20:27:07 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-11 09:50:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2021-10-28 16:04:09 +00:00
|
|
|
use crate::domain::{error::Result, handler::*, opaque_handler::*};
|
|
|
|
use async_trait::async_trait;
|
2021-10-24 09:03:09 +00:00
|
|
|
use ldap3_server::proto::{LdapDerefAliases, LdapSearchScope};
|
2021-03-12 08:33:43 +00:00
|
|
|
use mockall::predicate::eq;
|
2021-10-28 16:04:09 +00:00
|
|
|
use std::collections::HashSet;
|
2021-04-07 18:14:21 +00:00
|
|
|
use tokio;
|
2021-03-11 09:50:15 +00:00
|
|
|
|
2021-10-28 16:04:09 +00:00
|
|
|
mockall::mock! {
|
|
|
|
pub TestBackendHandler{}
|
|
|
|
impl Clone for TestBackendHandler {
|
|
|
|
fn clone(&self) -> Self;
|
|
|
|
}
|
|
|
|
#[async_trait]
|
|
|
|
impl LoginHandler for TestBackendHandler {
|
|
|
|
async fn bind(&self, request: BindRequest) -> Result<()>;
|
|
|
|
}
|
|
|
|
#[async_trait]
|
|
|
|
impl BackendHandler for TestBackendHandler {
|
2022-02-11 08:10:28 +00:00
|
|
|
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>;
|
2022-02-12 10:00:02 +00:00
|
|
|
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
2022-03-26 17:00:37 +00:00
|
|
|
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
2021-10-28 16:04:09 +00:00
|
|
|
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
2022-03-26 17:00:37 +00:00
|
|
|
async fn get_user_groups(&self, user: &UserId) -> Result<HashSet<GroupIdAndName>>;
|
2021-10-28 16:04:09 +00:00
|
|
|
async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
|
|
|
|
async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
|
|
|
|
async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
|
2022-03-26 17:00:37 +00:00
|
|
|
async fn delete_user(&self, user_id: &UserId) -> Result<()>;
|
2021-10-28 16:04:09 +00:00
|
|
|
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
|
|
|
|
async fn delete_group(&self, group_id: GroupId) -> Result<()>;
|
2022-03-26 17:00:37 +00:00
|
|
|
async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
|
|
|
|
async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
|
2021-10-28 16:04:09 +00:00
|
|
|
}
|
|
|
|
#[async_trait]
|
|
|
|
impl OpaqueHandler for TestBackendHandler {
|
|
|
|
async fn login_start(
|
|
|
|
&self,
|
|
|
|
request: login::ClientLoginStartRequest
|
|
|
|
) -> Result<login::ServerLoginStartResponse>;
|
2022-03-26 17:00:37 +00:00
|
|
|
async fn login_finish(&self, request: login::ClientLoginFinishRequest) -> Result<UserId>;
|
2021-10-28 16:04:09 +00:00
|
|
|
async fn registration_start(
|
|
|
|
&self,
|
|
|
|
request: registration::ClientRegistrationStartRequest
|
|
|
|
) -> Result<registration::ServerRegistrationStartResponse>;
|
|
|
|
async fn registration_finish(
|
|
|
|
&self,
|
|
|
|
request: registration::ClientRegistrationFinishRequest
|
|
|
|
) -> Result<()>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-24 09:03:09 +00:00
|
|
|
fn make_search_request<S: Into<String>>(
|
2021-10-28 16:04:09 +00:00
|
|
|
base: &str,
|
2021-10-24 09:03:09 +00:00
|
|
|
filter: LdapFilter,
|
|
|
|
attrs: Vec<S>,
|
|
|
|
) -> LdapSearchRequest {
|
|
|
|
LdapSearchRequest {
|
2021-10-28 16:04:09 +00:00
|
|
|
base: base.to_string(),
|
2021-10-24 09:03:09 +00:00
|
|
|
scope: LdapSearchScope::Base,
|
|
|
|
aliases: LdapDerefAliases::Never,
|
|
|
|
sizelimit: 0,
|
|
|
|
timelimit: 0,
|
|
|
|
typesonly: false,
|
|
|
|
filter,
|
|
|
|
attrs: attrs.into_iter().map(Into::into).collect(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-28 16:04:09 +00:00
|
|
|
fn make_user_search_request<S: Into<String>>(
|
|
|
|
filter: LdapFilter,
|
|
|
|
attrs: Vec<S>,
|
|
|
|
) -> LdapSearchRequest {
|
2022-05-30 15:59:02 +00:00
|
|
|
make_search_request::<S>("ou=people,Dc=example,dc=com", filter, attrs)
|
2021-10-28 16:04:09 +00:00
|
|
|
}
|
|
|
|
|
2021-04-07 18:55:23 +00:00
|
|
|
async fn setup_bound_handler(
|
|
|
|
mut mock: MockTestBackendHandler,
|
|
|
|
) -> LdapHandler<MockTestBackendHandler> {
|
2021-05-13 17:31:37 +00:00
|
|
|
mock.expect_bind()
|
|
|
|
.with(eq(BindRequest {
|
2022-03-26 17:00:37 +00:00
|
|
|
name: UserId::new("test"),
|
2021-05-13 17:31:37 +00:00
|
|
|
password: "pass".to_string(),
|
|
|
|
}))
|
|
|
|
.return_once(|_| Ok(()));
|
2022-05-08 18:12:06 +00:00
|
|
|
mock.expect_get_user_groups()
|
|
|
|
.with(eq(UserId::new("test")))
|
|
|
|
.return_once(|_| {
|
|
|
|
let mut set = HashSet::new();
|
|
|
|
set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string()));
|
|
|
|
Ok(set)
|
|
|
|
});
|
2022-05-30 15:59:02 +00:00
|
|
|
let mut ldap_handler = LdapHandler::new(mock, "dc=Example,dc=com".to_string());
|
2021-10-24 09:03:09 +00:00
|
|
|
let request = LdapBindRequest {
|
2022-05-30 15:59:02 +00:00
|
|
|
dn: "uid=test,ou=people,dc=example,dc=coM".to_string(),
|
2021-10-24 09:03:09 +00:00
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
2021-04-07 18:55:23 +00:00
|
|
|
};
|
2021-10-24 09:03:09 +00:00
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_bind(&request).await.0,
|
|
|
|
LdapResultCode::Success
|
|
|
|
);
|
2021-04-07 18:55:23 +00:00
|
|
|
ldap_handler
|
|
|
|
}
|
|
|
|
|
2021-04-07 18:14:21 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_bind() {
|
2021-05-13 17:31:37 +00:00
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_bind()
|
|
|
|
.with(eq(crate::domain::handler::BindRequest {
|
2022-03-26 17:00:37 +00:00
|
|
|
name: UserId::new("bob"),
|
2021-05-13 17:31:37 +00:00
|
|
|
password: "pass".to_string(),
|
|
|
|
}))
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| Ok(()));
|
2022-05-08 18:12:06 +00:00
|
|
|
mock.expect_get_user_groups()
|
|
|
|
.with(eq(UserId::new("bob")))
|
|
|
|
.return_once(|_| Ok(HashSet::new()));
|
2022-05-30 15:59:02 +00:00
|
|
|
let mut ldap_handler = LdapHandler::new(mock, "dc=eXample,dc=com".to_string());
|
2021-05-13 17:31:37 +00:00
|
|
|
|
2021-11-08 08:47:19 +00:00
|
|
|
let request = LdapOp::BindRequest(LdapBindRequest {
|
2022-04-29 07:46:08 +00:00
|
|
|
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
|
2021-10-24 09:03:09 +00:00
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
2021-11-08 08:47:19 +00:00
|
|
|
});
|
2021-05-13 17:31:37 +00:00
|
|
|
assert_eq!(
|
2021-11-08 08:47:19 +00:00
|
|
|
ldap_handler.handle_ldap_message(request).await,
|
|
|
|
Some(vec![LdapOp::BindResponse(LdapBindResponse {
|
|
|
|
res: LdapResult {
|
|
|
|
code: LdapResultCode::Success,
|
|
|
|
matcheddn: "".to_string(),
|
|
|
|
message: "".to_string(),
|
|
|
|
referral: vec![],
|
|
|
|
},
|
|
|
|
saslcreds: None,
|
|
|
|
})]),
|
2021-05-13 17:31:37 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_admin_bind() {
|
2021-03-11 09:50:15 +00:00
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_bind()
|
|
|
|
.with(eq(crate::domain::handler::BindRequest {
|
2022-03-26 17:00:37 +00:00
|
|
|
name: UserId::new("test"),
|
2021-03-11 09:50:15 +00:00
|
|
|
password: "pass".to_string(),
|
|
|
|
}))
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| Ok(()));
|
2022-05-08 18:12:06 +00:00
|
|
|
mock.expect_get_user_groups()
|
|
|
|
.with(eq(UserId::new("test")))
|
|
|
|
.return_once(|_| {
|
|
|
|
let mut set = HashSet::new();
|
|
|
|
set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string()));
|
|
|
|
Ok(set)
|
|
|
|
});
|
|
|
|
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
|
2021-03-11 09:50:15 +00:00
|
|
|
|
2021-10-24 09:03:09 +00:00
|
|
|
let request = LdapBindRequest {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
2021-10-24 09:03:09 +00:00
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
2021-03-11 09:50:15 +00:00
|
|
|
};
|
|
|
|
assert_eq!(
|
2021-10-24 09:03:09 +00:00
|
|
|
ldap_handler.do_bind(&request).await.0,
|
|
|
|
LdapResultCode::Success
|
2021-03-11 09:50:15 +00:00
|
|
|
);
|
|
|
|
}
|
2021-03-16 17:27:31 +00:00
|
|
|
|
2021-04-07 18:14:21 +00:00
|
|
|
#[tokio::test]
|
2022-04-25 07:03:55 +00:00
|
|
|
async fn test_search_non_admin_user() {
|
2021-04-07 18:14:21 +00:00
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_bind()
|
|
|
|
.with(eq(crate::domain::handler::BindRequest {
|
2022-03-26 17:00:37 +00:00
|
|
|
name: UserId::new("test"),
|
2021-04-07 18:14:21 +00:00
|
|
|
password: "pass".to_string(),
|
|
|
|
}))
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| Ok(()));
|
2022-04-25 07:03:55 +00:00
|
|
|
mock.expect_list_users()
|
|
|
|
.with(eq(Some(UserRequestFilter::And(vec![
|
|
|
|
UserRequestFilter::And(vec![]),
|
|
|
|
UserRequestFilter::UserId(UserId::new("test")),
|
|
|
|
]))))
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| {
|
|
|
|
Ok(vec![User {
|
2022-05-02 13:29:52 +00:00
|
|
|
user_id: UserId::new("test"),
|
2022-04-25 07:03:55 +00:00
|
|
|
..Default::default()
|
|
|
|
}])
|
|
|
|
});
|
2022-05-08 18:12:06 +00:00
|
|
|
mock.expect_get_user_groups()
|
|
|
|
.with(eq(UserId::new("test")))
|
|
|
|
.return_once(|_| Ok(HashSet::new()));
|
|
|
|
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
|
2021-04-07 18:14:21 +00:00
|
|
|
|
2021-10-24 09:03:09 +00:00
|
|
|
let request = LdapBindRequest {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
2021-10-24 09:03:09 +00:00
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
2021-04-07 18:14:21 +00:00
|
|
|
};
|
|
|
|
assert_eq!(
|
2021-10-24 09:03:09 +00:00
|
|
|
ldap_handler.do_bind(&request).await.0,
|
|
|
|
LdapResultCode::Success
|
2021-04-07 18:14:21 +00:00
|
|
|
);
|
|
|
|
|
2022-04-25 07:03:55 +00:00
|
|
|
let request =
|
|
|
|
make_user_search_request::<String>(LdapFilter::And(vec![]), vec!["1.1".to_string()]);
|
2021-04-07 18:14:21 +00:00
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
2022-04-25 07:03:55 +00:00
|
|
|
vec![
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
2022-04-25 07:03:55 +00:00
|
|
|
attributes: vec![],
|
|
|
|
}),
|
|
|
|
make_search_success()
|
|
|
|
],
|
2021-04-07 18:14:21 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-13 17:31:37 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_bind_invalid_dn() {
|
|
|
|
let mock = MockTestBackendHandler::new();
|
2022-05-08 18:12:06 +00:00
|
|
|
let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
|
2021-05-13 17:31:37 +00:00
|
|
|
|
2021-10-24 09:03:09 +00:00
|
|
|
let request = LdapBindRequest {
|
2021-05-13 17:31:37 +00:00
|
|
|
dn: "cn=bob,dc=example,dc=com".to_string(),
|
2021-10-24 09:03:09 +00:00
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
2021-05-13 17:31:37 +00:00
|
|
|
};
|
|
|
|
assert_eq!(
|
2021-10-24 09:03:09 +00:00
|
|
|
ldap_handler.do_bind(&request).await.0,
|
|
|
|
LdapResultCode::NamingViolation,
|
2021-05-13 17:31:37 +00:00
|
|
|
);
|
2021-10-24 09:03:09 +00:00
|
|
|
let request = LdapBindRequest {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=bob,dc=example,dc=com".to_string(),
|
2021-10-24 09:03:09 +00:00
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
2021-05-13 17:31:37 +00:00
|
|
|
};
|
|
|
|
assert_eq!(
|
2021-11-08 09:14:07 +00:00
|
|
|
ldap_handler.do_bind(&request).await.0,
|
|
|
|
LdapResultCode::NamingViolation,
|
|
|
|
);
|
|
|
|
let request = LdapBindRequest {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=bob,ou=groups,dc=example,dc=com".to_string(),
|
2021-11-08 09:14:07 +00:00
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
|
|
|
};
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_bind(&request).await.0,
|
|
|
|
LdapResultCode::NamingViolation,
|
|
|
|
);
|
|
|
|
let request = LdapBindRequest {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=bob,ou=people,dc=example,dc=fr".to_string(),
|
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
|
|
|
};
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_bind(&request).await.0,
|
|
|
|
LdapResultCode::NamingViolation,
|
|
|
|
);
|
|
|
|
let request = LdapBindRequest {
|
|
|
|
dn: "uid=bob=test,ou=people,dc=example,dc=com".to_string(),
|
2021-11-08 09:14:07 +00:00
|
|
|
cred: LdapBindCred::Simple("pass".to_string()),
|
|
|
|
};
|
|
|
|
assert_eq!(
|
2021-10-24 09:03:09 +00:00
|
|
|
ldap_handler.do_bind(&request).await.0,
|
|
|
|
LdapResultCode::NamingViolation,
|
2021-05-13 17:31:37 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-16 17:27:31 +00:00
|
|
|
#[test]
|
|
|
|
fn test_is_subtree() {
|
2021-03-22 08:59:58 +00:00
|
|
|
let subtree1 = &[
|
2021-03-16 17:27:31 +00:00
|
|
|
("ou".to_string(), "people".to_string()),
|
|
|
|
("dc".to_string(), "example".to_string()),
|
|
|
|
("dc".to_string(), "com".to_string()),
|
|
|
|
];
|
2021-03-22 08:59:58 +00:00
|
|
|
let root = &[
|
2021-03-16 17:27:31 +00:00
|
|
|
("dc".to_string(), "example".to_string()),
|
|
|
|
("dc".to_string(), "com".to_string()),
|
|
|
|
];
|
2021-03-22 08:59:58 +00:00
|
|
|
assert!(is_subtree(subtree1, root));
|
|
|
|
assert!(!is_subtree(&[], root));
|
2021-03-16 17:27:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_distinguished_name() {
|
2021-03-22 08:59:58 +00:00
|
|
|
let parsed_dn = &[
|
2021-03-16 17:27:31 +00:00
|
|
|
("ou".to_string(), "people".to_string()),
|
|
|
|
("dc".to_string(), "example".to_string()),
|
|
|
|
("dc".to_string(), "com".to_string()),
|
|
|
|
];
|
|
|
|
assert_eq!(
|
|
|
|
parse_distinguished_name("ou=people,dc=example,dc=com").expect("parsing failed"),
|
|
|
|
parsed_dn
|
|
|
|
);
|
2022-02-11 07:40:10 +00:00
|
|
|
assert_eq!(
|
|
|
|
parse_distinguished_name(" ou = people , dc = example , dc = com ")
|
|
|
|
.expect("parsing failed"),
|
|
|
|
parsed_dn
|
|
|
|
);
|
2021-03-16 17:27:31 +00:00
|
|
|
}
|
|
|
|
|
2021-04-07 18:14:21 +00:00
|
|
|
#[tokio::test]
|
2021-10-28 16:04:09 +00:00
|
|
|
async fn test_search_users() {
|
2021-11-06 17:06:23 +00:00
|
|
|
use chrono::prelude::*;
|
2021-03-16 17:27:31 +00:00
|
|
|
let mut mock = MockTestBackendHandler::new();
|
2021-04-07 18:55:23 +00:00
|
|
|
mock.expect_list_users().times(1).return_once(|_| {
|
|
|
|
Ok(vec![
|
|
|
|
User {
|
2022-03-26 17:00:37 +00:00
|
|
|
user_id: UserId::new("bob_1"),
|
2021-04-07 18:55:23 +00:00
|
|
|
email: "bob@bobmail.bob".to_string(),
|
2021-09-01 07:59:01 +00:00
|
|
|
display_name: "Bôb Böbberson".to_string(),
|
|
|
|
first_name: "Bôb".to_string(),
|
|
|
|
last_name: "Böbberson".to_string(),
|
2021-08-30 06:48:06 +00:00
|
|
|
..Default::default()
|
2021-04-07 18:55:23 +00:00
|
|
|
},
|
|
|
|
User {
|
2022-03-26 17:00:37 +00:00
|
|
|
user_id: UserId::new("jim"),
|
2021-04-07 18:55:23 +00:00
|
|
|
email: "jim@cricket.jim".to_string(),
|
2021-09-01 07:59:01 +00:00
|
|
|
display_name: "Jimminy Cricket".to_string(),
|
|
|
|
first_name: "Jim".to_string(),
|
|
|
|
last_name: "Cricket".to_string(),
|
2021-11-06 17:06:23 +00:00
|
|
|
creation_date: Utc.ymd(2014, 7, 8).and_hms(9, 10, 11),
|
2021-04-07 18:55:23 +00:00
|
|
|
},
|
|
|
|
])
|
|
|
|
});
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
2021-10-28 16:04:09 +00:00
|
|
|
let request = make_user_search_request(
|
2021-10-24 09:03:09 +00:00
|
|
|
LdapFilter::And(vec![]),
|
2021-11-06 17:06:23 +00:00
|
|
|
vec![
|
|
|
|
"objectClass",
|
|
|
|
"dn",
|
|
|
|
"uid",
|
|
|
|
"mail",
|
|
|
|
"givenName",
|
|
|
|
"sn",
|
|
|
|
"cn",
|
|
|
|
"createTimestamp",
|
|
|
|
],
|
2021-10-24 09:03:09 +00:00
|
|
|
);
|
2021-03-16 17:27:31 +00:00
|
|
|
assert_eq!(
|
2021-04-07 18:14:21 +00:00
|
|
|
ldap_handler.do_search(&request).await,
|
2021-03-16 17:27:31 +00:00
|
|
|
vec![
|
2021-10-24 09:03:09 +00:00
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=bob_1,ou=people,dc=example,dc=com".to_string(),
|
2021-03-16 17:27:31 +00:00
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectClass".to_string(),
|
2021-03-22 08:59:58 +00:00
|
|
|
vals: vec![
|
|
|
|
"inetOrgPerson".to_string(),
|
|
|
|
"posixAccount".to_string(),
|
2021-10-23 16:01:35 +00:00
|
|
|
"mailAccount".to_string(),
|
|
|
|
"person".to_string()
|
2021-03-22 08:59:58 +00:00
|
|
|
]
|
2021-03-16 17:27:31 +00:00
|
|
|
},
|
2021-10-28 14:24:01 +00:00
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "dn".to_string(),
|
2022-05-02 13:29:52 +00:00
|
|
|
vals: vec!["uid=bob_1,ou=people,dc=example,dc=com".to_string()]
|
2021-10-28 14:24:01 +00:00
|
|
|
},
|
2021-03-16 17:27:31 +00:00
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "uid".to_string(),
|
|
|
|
vals: vec!["bob_1".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "mail".to_string(),
|
|
|
|
vals: vec!["bob@bobmail.bob".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "givenName".to_string(),
|
|
|
|
vals: vec!["Bôb".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "sn".to_string(),
|
|
|
|
vals: vec!["Böbberson".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
|
|
|
vals: vec!["Bôb Böbberson".to_string()]
|
2021-11-06 17:06:23 +00:00
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "createTimestamp".to_string(),
|
|
|
|
vals: vec!["1970-01-01T00:00:00+00:00".to_string()]
|
2021-03-16 17:27:31 +00:00
|
|
|
}
|
|
|
|
],
|
|
|
|
}),
|
2021-10-24 09:03:09 +00:00
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=jim,ou=people,dc=example,dc=com".to_string(),
|
2021-03-16 17:27:31 +00:00
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectClass".to_string(),
|
2021-03-22 08:59:58 +00:00
|
|
|
vals: vec![
|
|
|
|
"inetOrgPerson".to_string(),
|
|
|
|
"posixAccount".to_string(),
|
2021-10-23 16:01:35 +00:00
|
|
|
"mailAccount".to_string(),
|
|
|
|
"person".to_string()
|
2021-03-22 08:59:58 +00:00
|
|
|
]
|
2021-03-16 17:27:31 +00:00
|
|
|
},
|
2021-10-28 14:24:01 +00:00
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "dn".to_string(),
|
2022-05-02 13:29:52 +00:00
|
|
|
vals: vec!["uid=jim,ou=people,dc=example,dc=com".to_string()]
|
2021-10-28 14:24:01 +00:00
|
|
|
},
|
2021-03-16 17:27:31 +00:00
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "uid".to_string(),
|
|
|
|
vals: vec!["jim".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "mail".to_string(),
|
|
|
|
vals: vec!["jim@cricket.jim".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "givenName".to_string(),
|
|
|
|
vals: vec!["Jim".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "sn".to_string(),
|
|
|
|
vals: vec!["Cricket".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
|
|
|
vals: vec!["Jimminy Cricket".to_string()]
|
2021-11-06 17:06:23 +00:00
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "createTimestamp".to_string(),
|
|
|
|
vals: vec!["2014-07-08T09:10:11+00:00".to_string()]
|
2021-03-16 17:27:31 +00:00
|
|
|
}
|
|
|
|
],
|
|
|
|
}),
|
2021-10-24 09:03:09 +00:00
|
|
|
make_search_success(),
|
2021-03-16 17:27:31 +00:00
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
2021-04-07 18:55:23 +00:00
|
|
|
|
2021-10-28 16:04:09 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_groups() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
2022-02-12 10:00:02 +00:00
|
|
|
mock.expect_list_groups()
|
|
|
|
.with(eq(Some(GroupRequestFilter::And(vec![]))))
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| {
|
|
|
|
Ok(vec![
|
|
|
|
Group {
|
|
|
|
id: GroupId(1),
|
|
|
|
display_name: "group_1".to_string(),
|
2022-03-26 17:00:37 +00:00
|
|
|
users: vec![UserId::new("bob"), UserId::new("john")],
|
2022-02-12 10:00:02 +00:00
|
|
|
},
|
|
|
|
Group {
|
|
|
|
id: GroupId(3),
|
2022-05-30 15:59:02 +00:00
|
|
|
display_name: "BestGroup".to_string(),
|
2022-03-26 17:00:37 +00:00
|
|
|
users: vec![UserId::new("john")],
|
2022-02-12 10:00:02 +00:00
|
|
|
},
|
|
|
|
])
|
|
|
|
});
|
2021-10-28 16:04:09 +00:00
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
let request = make_search_request(
|
2022-05-30 15:59:02 +00:00
|
|
|
"ou=groups,dc=example,dc=cOm",
|
2021-10-28 16:04:09 +00:00
|
|
|
LdapFilter::And(vec![]),
|
|
|
|
vec!["objectClass", "dn", "cn", "uniqueMember"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
|
|
|
dn: "cn=group_1,ou=groups,dc=example,dc=com".to_string(),
|
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectClass".to_string(),
|
|
|
|
vals: vec!["groupOfUniqueNames".to_string(),]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "dn".to_string(),
|
|
|
|
vals: vec!["cn=group_1,ou=groups,dc=example,dc=com".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
|
|
|
vals: vec!["group_1".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "uniqueMember".to_string(),
|
|
|
|
vals: vec![
|
2022-05-02 13:29:52 +00:00
|
|
|
"uid=bob,ou=people,dc=example,dc=com".to_string(),
|
|
|
|
"uid=john,ou=people,dc=example,dc=com".to_string(),
|
2021-10-28 16:04:09 +00:00
|
|
|
]
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}),
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
2022-05-30 15:59:02 +00:00
|
|
|
dn: "cn=BestGroup,ou=groups,dc=example,dc=com".to_string(),
|
2021-10-28 16:04:09 +00:00
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectClass".to_string(),
|
|
|
|
vals: vec!["groupOfUniqueNames".to_string(),]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "dn".to_string(),
|
2022-05-30 15:59:02 +00:00
|
|
|
vals: vec!["cn=BestGroup,ou=groups,dc=example,dc=com".to_string()]
|
2021-10-28 16:04:09 +00:00
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
2022-05-30 15:59:02 +00:00
|
|
|
vals: vec!["BestGroup".to_string()]
|
2021-10-28 16:04:09 +00:00
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "uniqueMember".to_string(),
|
2022-05-02 13:29:52 +00:00
|
|
|
vals: vec!["uid=john,ou=people,dc=example,dc=com".to_string()]
|
2021-10-28 16:04:09 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
}),
|
|
|
|
make_search_success(),
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_groups_filter() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
2022-02-12 10:00:02 +00:00
|
|
|
mock.expect_list_groups()
|
|
|
|
.with(eq(Some(GroupRequestFilter::And(vec![
|
|
|
|
GroupRequestFilter::DisplayName("group_1".to_string()),
|
2022-03-26 17:00:37 +00:00
|
|
|
GroupRequestFilter::Member(UserId::new("bob")),
|
2022-02-12 10:00:02 +00:00
|
|
|
GroupRequestFilter::And(vec![]),
|
2022-04-19 15:58:40 +00:00
|
|
|
GroupRequestFilter::And(vec![]),
|
2022-05-29 15:41:37 +00:00
|
|
|
GroupRequestFilter::And(vec![]),
|
|
|
|
GroupRequestFilter::And(vec![]),
|
|
|
|
GroupRequestFilter::Not(Box::new(GroupRequestFilter::Not(Box::new(
|
|
|
|
GroupRequestFilter::And(vec![]),
|
|
|
|
)))),
|
2022-02-12 10:00:02 +00:00
|
|
|
]))))
|
2021-10-28 16:04:09 +00:00
|
|
|
.times(1)
|
|
|
|
.return_once(|_| {
|
2022-02-12 10:00:02 +00:00
|
|
|
Ok(vec![Group {
|
|
|
|
display_name: "group_1".to_string(),
|
|
|
|
id: GroupId(1),
|
|
|
|
users: vec![],
|
2021-10-28 16:04:09 +00:00
|
|
|
}])
|
|
|
|
});
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
let request = make_search_request(
|
|
|
|
"ou=groups,dc=example,dc=com",
|
2021-11-08 08:21:32 +00:00
|
|
|
LdapFilter::And(vec![
|
2022-05-30 15:59:02 +00:00
|
|
|
LdapFilter::Equality("cN".to_string(), "Group_1".to_string()),
|
2021-11-08 08:21:32 +00:00
|
|
|
LdapFilter::Equality(
|
|
|
|
"uniqueMember".to_string(),
|
2022-05-30 15:59:02 +00:00
|
|
|
"uid=bob,ou=peopLe,Dc=eXample,dc=com".to_string(),
|
2021-11-08 08:21:32 +00:00
|
|
|
),
|
2022-05-30 15:59:02 +00:00
|
|
|
LdapFilter::Equality("obJEctclass".to_string(), "groupOfUniqueNames".to_string()),
|
2022-04-19 15:58:40 +00:00
|
|
|
LdapFilter::Equality("objectclass".to_string(), "groupOfNames".to_string()),
|
2022-05-29 15:41:37 +00:00
|
|
|
LdapFilter::Present("objectclass".to_string()),
|
|
|
|
LdapFilter::Present("dn".to_string()),
|
|
|
|
LdapFilter::Not(Box::new(LdapFilter::Present(
|
2022-05-30 15:59:02 +00:00
|
|
|
"random_attribUte".to_string(),
|
2022-05-29 15:41:37 +00:00
|
|
|
))),
|
2021-11-08 08:21:32 +00:00
|
|
|
]),
|
2022-04-25 07:03:55 +00:00
|
|
|
vec!["1.1"],
|
2021-10-28 16:04:09 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
|
|
|
dn: "cn=group_1,ou=groups,dc=example,dc=com".to_string(),
|
2022-04-25 07:03:55 +00:00
|
|
|
attributes: vec![],
|
2021-10-28 16:04:09 +00:00
|
|
|
}),
|
|
|
|
make_search_success(),
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-02-12 10:00:02 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_groups_filter_2() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_list_groups()
|
|
|
|
.with(eq(Some(GroupRequestFilter::Or(vec![
|
|
|
|
GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
|
|
|
|
"group_2".to_string(),
|
|
|
|
))),
|
|
|
|
]))))
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| {
|
|
|
|
Ok(vec![Group {
|
|
|
|
display_name: "group_1".to_string(),
|
|
|
|
id: GroupId(1),
|
|
|
|
users: vec![],
|
|
|
|
}])
|
|
|
|
});
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
let request = make_search_request(
|
|
|
|
"ou=groups,dc=example,dc=com",
|
|
|
|
LdapFilter::Or(vec![LdapFilter::Not(Box::new(LdapFilter::Equality(
|
|
|
|
"displayname".to_string(),
|
|
|
|
"group_2".to_string(),
|
|
|
|
)))]),
|
|
|
|
vec!["cn"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
|
|
|
dn: "cn=group_1,ou=groups,dc=example,dc=com".to_string(),
|
|
|
|
attributes: vec![LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
|
|
|
vals: vec!["group_1".to_string()]
|
|
|
|
},],
|
|
|
|
}),
|
|
|
|
make_search_success(),
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_groups_error() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_list_groups()
|
|
|
|
.with(eq(Some(GroupRequestFilter::Or(vec![
|
|
|
|
GroupRequestFilter::Not(Box::new(GroupRequestFilter::DisplayName(
|
|
|
|
"group_2".to_string(),
|
|
|
|
))),
|
|
|
|
]))))
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| {
|
|
|
|
Err(crate::domain::error::DomainError::InternalError(
|
|
|
|
"Error getting groups".to_string(),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
let request = make_search_request(
|
|
|
|
"ou=groups,dc=example,dc=com",
|
|
|
|
LdapFilter::Or(vec![LdapFilter::Not(Box::new(LdapFilter::Equality(
|
|
|
|
"displayname".to_string(),
|
|
|
|
"group_2".to_string(),
|
|
|
|
)))]),
|
|
|
|
vec!["cn"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![make_search_error(
|
|
|
|
LdapResultCode::Other,
|
|
|
|
r#"Error while listing groups "ou=groups,dc=example,dc=com": Internal error: `Error getting groups`"#.to_string()
|
|
|
|
)]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_groups_filter_error() {
|
|
|
|
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await;
|
|
|
|
let request = make_search_request(
|
|
|
|
"ou=groups,dc=example,dc=com",
|
|
|
|
LdapFilter::And(vec![LdapFilter::Equality(
|
|
|
|
"whatever".to_string(),
|
|
|
|
"group_1".to_string(),
|
|
|
|
)]),
|
|
|
|
vec!["cn"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![make_search_error(
|
|
|
|
LdapResultCode::UnwillingToPerform,
|
|
|
|
"Unsupported group filter: Unknown field: whatever".to_string()
|
|
|
|
)]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-04-07 18:55:23 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_filters() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_list_users()
|
2022-02-12 10:00:02 +00:00
|
|
|
.with(eq(Some(UserRequestFilter::And(vec![
|
|
|
|
UserRequestFilter::Or(vec![
|
2022-03-26 17:00:37 +00:00
|
|
|
UserRequestFilter::Not(Box::new(UserRequestFilter::UserId(UserId::new("bob")))),
|
2022-02-12 10:00:02 +00:00
|
|
|
UserRequestFilter::And(vec![]),
|
|
|
|
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
|
|
|
|
UserRequestFilter::And(vec![]),
|
|
|
|
UserRequestFilter::And(vec![]),
|
|
|
|
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
|
|
|
|
]),
|
|
|
|
]))))
|
2021-04-07 18:55:23 +00:00
|
|
|
.times(1)
|
|
|
|
.return_once(|_| Ok(vec![]));
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
2021-10-28 16:04:09 +00:00
|
|
|
let request = make_user_search_request(
|
2021-11-08 10:03:16 +00:00
|
|
|
LdapFilter::And(vec![LdapFilter::Or(vec![
|
|
|
|
LdapFilter::Not(Box::new(LdapFilter::Equality(
|
|
|
|
"uid".to_string(),
|
|
|
|
"bob".to_string(),
|
|
|
|
))),
|
|
|
|
LdapFilter::Equality("objectclass".to_string(), "person".to_string()),
|
|
|
|
LdapFilter::Equality("objectclass".to_string(), "other".to_string()),
|
|
|
|
LdapFilter::Present("objectClass".to_string()),
|
|
|
|
LdapFilter::Present("uid".to_string()),
|
|
|
|
LdapFilter::Present("unknown".to_string()),
|
|
|
|
])]),
|
2021-10-24 09:03:09 +00:00
|
|
|
vec!["objectClass"],
|
|
|
|
);
|
2021-04-07 18:55:23 +00:00
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
2021-10-24 09:03:09 +00:00
|
|
|
vec![make_search_success()]
|
2021-04-07 18:55:23 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-08 09:14:26 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_member_of() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_list_users()
|
2022-02-11 08:10:28 +00:00
|
|
|
.with(eq(Some(UserRequestFilter::MemberOf("group_1".to_string()))))
|
2021-11-08 09:14:26 +00:00
|
|
|
.times(1)
|
|
|
|
.return_once(|_| Ok(vec![]));
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
let request = make_user_search_request(
|
|
|
|
LdapFilter::Equality(
|
|
|
|
"memberOf".to_string(),
|
2022-02-11 07:40:10 +00:00
|
|
|
"cn=group_1, ou=groups, dc=example,dc=com".to_string(),
|
2021-11-08 09:14:26 +00:00
|
|
|
),
|
|
|
|
vec!["objectClass"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![make_search_success()]
|
|
|
|
);
|
|
|
|
let request = make_user_search_request(
|
|
|
|
LdapFilter::Equality("memberOf".to_string(), "group_1".to_string()),
|
|
|
|
vec!["objectClass"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![make_search_error(
|
|
|
|
LdapResultCode::UnwillingToPerform,
|
|
|
|
"Unsupported user filter: while parsing a group ID: Missing DN value".to_string()
|
|
|
|
)]
|
|
|
|
);
|
|
|
|
let request = make_user_search_request(
|
|
|
|
LdapFilter::Equality(
|
|
|
|
"memberOf".to_string(),
|
|
|
|
"cn=mygroup,dc=example,dc=com".to_string(),
|
|
|
|
),
|
|
|
|
vec!["objectClass"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![make_search_error(
|
|
|
|
LdapResultCode::UnwillingToPerform,
|
|
|
|
"Unsupported user filter: Unexpected group DN format. Got \"cn=mygroup,dc=example,dc=com\", expected: \"cn=groupname,ou=groups,dc=example,dc=com\"".to_string()
|
|
|
|
)]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-07 13:56:07 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_filters_lowercase() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_list_users()
|
2022-02-12 10:00:02 +00:00
|
|
|
.with(eq(Some(UserRequestFilter::And(vec![
|
|
|
|
UserRequestFilter::Or(vec![UserRequestFilter::Not(Box::new(
|
|
|
|
UserRequestFilter::Equality("first_name".to_string(), "bob".to_string()),
|
|
|
|
))]),
|
|
|
|
]))))
|
2021-11-07 13:56:07 +00:00
|
|
|
.times(1)
|
|
|
|
.return_once(|_| {
|
|
|
|
Ok(vec![User {
|
2022-05-02 13:29:52 +00:00
|
|
|
user_id: UserId::new("bob_1"),
|
2021-11-07 13:56:07 +00:00
|
|
|
..Default::default()
|
|
|
|
}])
|
|
|
|
});
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
let request = make_user_search_request(
|
|
|
|
LdapFilter::And(vec![LdapFilter::Or(vec![LdapFilter::Not(Box::new(
|
|
|
|
LdapFilter::Equality("givenname".to_string(), "bob".to_string()),
|
|
|
|
))])]),
|
|
|
|
vec!["objectclass"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=bob_1,ou=people,dc=example,dc=com".to_string(),
|
2021-11-07 13:56:07 +00:00
|
|
|
attributes: vec![LdapPartialAttribute {
|
|
|
|
atype: "objectclass".to_string(),
|
|
|
|
vals: vec![
|
|
|
|
"inetOrgPerson".to_string(),
|
|
|
|
"posixAccount".to_string(),
|
|
|
|
"mailAccount".to_string(),
|
|
|
|
"person".to_string()
|
|
|
|
]
|
|
|
|
},]
|
|
|
|
}),
|
|
|
|
make_search_success()
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_both() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
mock.expect_list_users().times(1).return_once(|_| {
|
|
|
|
Ok(vec![User {
|
2022-03-26 17:00:37 +00:00
|
|
|
user_id: UserId::new("bob_1"),
|
2021-11-07 13:56:07 +00:00
|
|
|
email: "bob@bobmail.bob".to_string(),
|
|
|
|
display_name: "Bôb Böbberson".to_string(),
|
|
|
|
first_name: "Bôb".to_string(),
|
|
|
|
last_name: "Böbberson".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
}])
|
|
|
|
});
|
2022-02-12 10:00:02 +00:00
|
|
|
mock.expect_list_groups()
|
|
|
|
.with(eq(Some(GroupRequestFilter::And(vec![]))))
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| {
|
|
|
|
Ok(vec![Group {
|
|
|
|
id: GroupId(1),
|
|
|
|
display_name: "group_1".to_string(),
|
2022-03-26 17:00:37 +00:00
|
|
|
users: vec![UserId::new("bob"), UserId::new("john")],
|
2022-02-12 10:00:02 +00:00
|
|
|
}])
|
|
|
|
});
|
2021-11-07 13:56:07 +00:00
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
let request = make_search_request(
|
|
|
|
"dc=example,dc=com",
|
|
|
|
LdapFilter::And(vec![]),
|
|
|
|
vec!["objectClass", "dn", "cn"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
2022-05-02 13:29:52 +00:00
|
|
|
dn: "uid=bob_1,ou=people,dc=example,dc=com".to_string(),
|
2021-11-07 13:56:07 +00:00
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectClass".to_string(),
|
|
|
|
vals: vec![
|
|
|
|
"inetOrgPerson".to_string(),
|
|
|
|
"posixAccount".to_string(),
|
|
|
|
"mailAccount".to_string(),
|
|
|
|
"person".to_string()
|
|
|
|
]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "dn".to_string(),
|
2022-05-02 13:29:52 +00:00
|
|
|
vals: vec!["uid=bob_1,ou=people,dc=example,dc=com".to_string()]
|
2021-11-07 13:56:07 +00:00
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
|
|
|
vals: vec!["Bôb Böbberson".to_string()]
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}),
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
|
|
|
dn: "cn=group_1,ou=groups,dc=example,dc=com".to_string(),
|
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectClass".to_string(),
|
|
|
|
vals: vec!["groupOfUniqueNames".to_string(),]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "dn".to_string(),
|
|
|
|
vals: vec!["cn=group_1,ou=groups,dc=example,dc=com".to_string()]
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
|
|
|
vals: vec!["group_1".to_string()]
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}),
|
|
|
|
make_search_success(),
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
2022-05-08 12:53:54 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_wildcards() {
|
|
|
|
use chrono::TimeZone;
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
|
|
|
|
mock.expect_list_users().returning(|_| {
|
|
|
|
Ok(vec![User {
|
|
|
|
user_id: UserId::new("bob_1"),
|
|
|
|
email: "bob@bobmail.bob".to_string(),
|
|
|
|
display_name: "Bôb Böbberson".to_string(),
|
|
|
|
first_name: "Bôb".to_string(),
|
|
|
|
last_name: "Böbberson".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
}])
|
|
|
|
});
|
|
|
|
mock.expect_list_groups()
|
|
|
|
.with(eq(Some(GroupRequestFilter::And(vec![]))))
|
|
|
|
.returning(|_| {
|
|
|
|
Ok(vec![Group {
|
|
|
|
id: GroupId(1),
|
|
|
|
display_name: "group_1".to_string(),
|
|
|
|
users: vec![UserId::new("bob"), UserId::new("john")],
|
|
|
|
}])
|
|
|
|
});
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
|
|
|
|
// Test simple wildcard
|
|
|
|
let request =
|
|
|
|
make_search_request("dc=example,dc=com", LdapFilter::And(vec![]), vec!["*", "+"]);
|
|
|
|
|
|
|
|
// all: "objectclass", "dn", "uid", "mail", "givenname", "sn", "cn"
|
|
|
|
// Operational: "createtimestamp"
|
|
|
|
|
|
|
|
let expected_result = vec![
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
|
|
|
dn: "uid=bob_1,ou=people,dc=example,dc=com".to_string(),
|
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectclass".to_string(),
|
|
|
|
vals: vec![
|
|
|
|
"inetOrgPerson".to_string(),
|
|
|
|
"posixAccount".to_string(),
|
|
|
|
"mailAccount".to_string(),
|
|
|
|
"person".to_string(),
|
|
|
|
],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "dn".to_string(),
|
|
|
|
vals: vec!["uid=bob_1,ou=people,dc=example,dc=com".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "uid".to_string(),
|
|
|
|
vals: vec!["bob_1".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "mail".to_string(),
|
|
|
|
vals: vec!["bob@bobmail.bob".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "givenname".to_string(),
|
|
|
|
vals: vec!["Bôb".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "sn".to_string(),
|
|
|
|
vals: vec!["Böbberson".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
|
|
|
vals: vec!["Bôb Böbberson".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "createtimestamp".to_string(),
|
|
|
|
vals: vec![chrono::Utc.timestamp(0, 0).to_rfc3339()],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}),
|
|
|
|
// "objectclass", "dn", "uid", "cn", "member", "uniquemember"
|
|
|
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
|
|
|
dn: "cn=group_1,ou=groups,dc=example,dc=com".to_string(),
|
|
|
|
attributes: vec![
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "objectclass".to_string(),
|
|
|
|
vals: vec!["groupOfUniqueNames".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "dn".to_string(),
|
|
|
|
vals: vec!["cn=group_1,ou=groups,dc=example,dc=com".to_string()],
|
|
|
|
},
|
|
|
|
// UID
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "uid".to_string(),
|
|
|
|
vals: vec!["group_1".to_string()],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "cn".to_string(),
|
|
|
|
vals: vec!["group_1".to_string()],
|
|
|
|
},
|
|
|
|
//member / uniquemember : "uid={},ou=people,{}"
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "member".to_string(),
|
|
|
|
vals: vec![
|
|
|
|
"uid=bob,ou=people,dc=example,dc=com".to_string(),
|
|
|
|
"uid=john,ou=people,dc=example,dc=com".to_string(),
|
|
|
|
],
|
|
|
|
},
|
|
|
|
LdapPartialAttribute {
|
|
|
|
atype: "uniquemember".to_string(),
|
|
|
|
vals: vec![
|
|
|
|
"uid=bob,ou=people,dc=example,dc=com".to_string(),
|
|
|
|
"uid=john,ou=people,dc=example,dc=com".to_string(),
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}),
|
|
|
|
make_search_success(),
|
|
|
|
];
|
|
|
|
|
|
|
|
assert_eq!(ldap_handler.do_search(&request).await, expected_result);
|
|
|
|
|
|
|
|
let request2 =
|
|
|
|
make_search_request("dc=example,dc=com", LdapFilter::And(vec![]), vec!["*", "*"]);
|
|
|
|
|
|
|
|
assert_eq!(ldap_handler.do_search(&request2).await, expected_result);
|
|
|
|
|
|
|
|
let request3 = make_search_request(
|
|
|
|
"dc=example,dc=com",
|
|
|
|
LdapFilter::And(vec![]),
|
|
|
|
vec!["*", "+", "+"],
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(ldap_handler.do_search(&request3).await, expected_result);
|
|
|
|
|
|
|
|
let request4 =
|
|
|
|
make_search_request("dc=example,dc=com", LdapFilter::And(vec![]), vec![""; 0]);
|
|
|
|
|
|
|
|
assert_eq!(ldap_handler.do_search(&request4).await, expected_result);
|
|
|
|
|
|
|
|
let request5 = make_search_request(
|
|
|
|
"dc=example,dc=com",
|
|
|
|
LdapFilter::And(vec![]),
|
|
|
|
vec!["objectclass", "dn", "uid", "*"],
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(ldap_handler.do_search(&request5).await, expected_result);
|
|
|
|
}
|
2021-11-07 13:56:07 +00:00
|
|
|
|
2021-11-07 13:57:34 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_wrong_base() {
|
|
|
|
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await;
|
|
|
|
let request = make_search_request(
|
|
|
|
"ou=users,dc=example,dc=com",
|
|
|
|
LdapFilter::And(vec![]),
|
|
|
|
vec!["objectClass"],
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![make_search_success()]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-04-07 18:55:23 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_unsupported_filters() {
|
2021-04-09 08:47:26 +00:00
|
|
|
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await;
|
2021-10-28 16:04:09 +00:00
|
|
|
let request = make_user_search_request(
|
2021-10-24 09:03:09 +00:00
|
|
|
LdapFilter::Substring(
|
2021-09-24 20:35:31 +00:00
|
|
|
"uid".to_string(),
|
|
|
|
ldap3_server::proto::LdapSubstringFilter::default(),
|
|
|
|
),
|
2021-10-24 09:03:09 +00:00
|
|
|
vec!["objectClass"],
|
|
|
|
);
|
2021-04-07 18:55:23 +00:00
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
2021-10-24 09:03:09 +00:00
|
|
|
vec![make_search_error(
|
2021-04-07 18:55:23 +00:00
|
|
|
LdapResultCode::UnwillingToPerform,
|
2021-10-23 16:01:35 +00:00
|
|
|
"Unsupported user filter: Unsupported user filter: Substring(\"uid\", LdapSubstringFilter { initial: None, any: [], final_: None })".to_string()
|
2021-04-07 18:55:23 +00:00
|
|
|
)]
|
|
|
|
);
|
|
|
|
}
|
2021-10-28 16:03:26 +00:00
|
|
|
|
2021-11-08 09:49:25 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_password_change() {
|
|
|
|
let mut mock = MockTestBackendHandler::new();
|
|
|
|
use lldap_auth::*;
|
|
|
|
let mut rng = rand::rngs::OsRng;
|
|
|
|
let registration_start_request =
|
|
|
|
opaque::client::registration::start_registration("password", &mut rng).unwrap();
|
|
|
|
let request = registration::ClientRegistrationStartRequest {
|
|
|
|
username: "bob".to_string(),
|
|
|
|
registration_start_request: registration_start_request.message,
|
|
|
|
};
|
|
|
|
let start_response = opaque::server::registration::start_registration(
|
|
|
|
&opaque::server::ServerSetup::new(&mut rng),
|
|
|
|
request.registration_start_request,
|
|
|
|
&request.username,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
mock.expect_registration_start().times(1).return_once(|_| {
|
|
|
|
Ok(registration::ServerRegistrationStartResponse {
|
|
|
|
server_data: "".to_string(),
|
|
|
|
registration_response: start_response.message,
|
|
|
|
})
|
|
|
|
});
|
|
|
|
mock.expect_registration_finish()
|
|
|
|
.times(1)
|
|
|
|
.return_once(|_| Ok(()));
|
|
|
|
let mut ldap_handler = setup_bound_handler(mock).await;
|
|
|
|
let request = LdapOp::ExtendedRequest(
|
|
|
|
LdapPasswordModifyRequest {
|
2022-05-02 13:29:52 +00:00
|
|
|
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
2021-11-08 09:49:25 +00:00
|
|
|
old_password: None,
|
|
|
|
new_password: Some("password".to_string()),
|
|
|
|
}
|
|
|
|
.into(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.handle_ldap_message(request).await,
|
|
|
|
Some(vec![make_extended_response(
|
|
|
|
LdapResultCode::Success,
|
|
|
|
"".to_string(),
|
|
|
|
)])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_password_change_errors() {
|
|
|
|
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await;
|
|
|
|
let request = LdapOp::ExtendedRequest(
|
|
|
|
LdapPasswordModifyRequest {
|
|
|
|
user_identity: None,
|
|
|
|
old_password: None,
|
|
|
|
new_password: None,
|
|
|
|
}
|
|
|
|
.into(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.handle_ldap_message(request).await,
|
|
|
|
Some(vec![make_extended_response(
|
|
|
|
LdapResultCode::ConstraintViolation,
|
|
|
|
"Missing either user_id or password".to_string(),
|
|
|
|
)])
|
|
|
|
);
|
|
|
|
let request = LdapOp::ExtendedRequest(
|
|
|
|
LdapPasswordModifyRequest {
|
2022-05-02 13:29:52 +00:00
|
|
|
user_identity: Some("uid=bob,ou=groups,ou=people,dc=example,dc=com".to_string()),
|
2021-11-08 09:49:25 +00:00
|
|
|
old_password: None,
|
|
|
|
new_password: Some("password".to_string()),
|
|
|
|
}
|
|
|
|
.into(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.handle_ldap_message(request).await,
|
|
|
|
Some(vec![make_extended_response(
|
|
|
|
LdapResultCode::InvalidDNSyntax,
|
2022-05-02 13:29:52 +00:00
|
|
|
r#"Invalid username: "Unexpected user DN format. Got \"uid=bob,ou=groups,ou=people,dc=example,dc=com\", expected: \"uid=username,ou=people,dc=example,dc=com\"""#.to_string(),
|
2021-11-08 09:49:25 +00:00
|
|
|
)])
|
|
|
|
);
|
|
|
|
let request = LdapOp::ExtendedRequest(LdapExtendedRequest {
|
|
|
|
name: "test".to_string(),
|
|
|
|
value: None,
|
|
|
|
});
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.handle_ldap_message(request).await,
|
|
|
|
Some(vec![make_extended_response(
|
|
|
|
LdapResultCode::UnwillingToPerform,
|
|
|
|
"Unsupported extended operation: test".to_string(),
|
|
|
|
)])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-10-28 16:03:26 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_search_root_dse() {
|
|
|
|
let mut ldap_handler = setup_bound_handler(MockTestBackendHandler::new()).await;
|
|
|
|
let request = LdapSearchRequest {
|
|
|
|
base: "".to_string(),
|
|
|
|
scope: LdapSearchScope::Base,
|
|
|
|
aliases: LdapDerefAliases::Never,
|
|
|
|
sizelimit: 0,
|
|
|
|
timelimit: 0,
|
|
|
|
typesonly: false,
|
|
|
|
filter: LdapFilter::Present("objectClass".to_string()),
|
|
|
|
attrs: vec!["supportedExtension".to_string()],
|
|
|
|
};
|
|
|
|
assert_eq!(
|
|
|
|
ldap_handler.do_search(&request).await,
|
|
|
|
vec![
|
|
|
|
root_dse_response("dc=example,dc=com"),
|
|
|
|
make_search_success()
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
2021-03-11 09:50:15 +00:00
|
|
|
}
|