mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
ldap: Implement group listing, fix various bugs
This commit is contained in:
parent
5a00b7d8bb
commit
107c8ec96e
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1740,6 +1740,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"time 0.2.27",
|
"time 0.2.27",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-actix-web",
|
"tracing-actix-web",
|
||||||
|
@ -24,6 +24,7 @@ RUN set -x \
|
|||||||
# Prepare the dependency list.
|
# Prepare the dependency list.
|
||||||
FROM chef AS planner
|
FROM chef AS planner
|
||||||
COPY . .
|
COPY . .
|
||||||
|
USER root
|
||||||
RUN cargo chef prepare --recipe-path recipe.json
|
RUN cargo chef prepare --recipe-path recipe.json
|
||||||
|
|
||||||
# Build dependencies
|
# Build dependencies
|
||||||
|
@ -38,10 +38,11 @@
|
|||||||
|
|
||||||
## Admin password.
|
## Admin password.
|
||||||
## Password for the admin account, both for the LDAP bind and for the
|
## Password for the admin account, both for the LDAP bind and for the
|
||||||
## administration interface.
|
## administration interface. It is only used when initially creating
|
||||||
|
## the admin user.
|
||||||
## It should be minimum 8 characters long.
|
## It should be minimum 8 characters long.
|
||||||
## You can set it with the LLDAP_LDAP_USER_PASS environment variable.
|
## You can set it with the LLDAP_LDAP_USER_PASS environment variable.
|
||||||
## Note: you can create another admin user for LDAP/administration, this
|
## Note: you can create another admin user for user administration, this
|
||||||
## is just the default one.
|
## is just the default one.
|
||||||
#ldap_user_pass = "REPLACE_WITH_PASSWORD"
|
#ldap_user_pass = "REPLACE_WITH_PASSWORD"
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ thiserror = "*"
|
|||||||
time = "0.2"
|
time = "0.2"
|
||||||
tokio = { version = "1.2.0", features = ["full"] }
|
tokio = { version = "1.2.0", features = ["full"] }
|
||||||
tokio-util = "0.6.3"
|
tokio-util = "0.6.3"
|
||||||
|
tokio-stream = "*"
|
||||||
tracing = "*"
|
tracing = "*"
|
||||||
tracing-actix-web = "0.4.0-beta.7"
|
tracing-actix-web = "0.4.0-beta.7"
|
||||||
tracing-log = "*"
|
tracing-log = "*"
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
use crate::domain::handler::{BackendHandler, LoginHandler, RequestFilter, User};
|
use crate::domain::handler::{
|
||||||
|
BackendHandler, Group, GroupIdAndName, LoginHandler, RequestFilter, User,
|
||||||
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
use futures_util::TryStreamExt;
|
||||||
use ldap3_server::simple::*;
|
use ldap3_server::simple::*;
|
||||||
|
use log::*;
|
||||||
|
|
||||||
fn make_dn_pair<I>(mut iter: I) -> Result<(String, String)>
|
fn make_dn_pair<I>(mut iter: I) -> Result<(String, String)>
|
||||||
where
|
where
|
||||||
@ -41,14 +46,16 @@ fn get_group_id_from_distinguished_name(
|
|||||||
if parts.len() == base_tree.len() + 2 {
|
if parts.len() == base_tree.len() + 2 {
|
||||||
if parts[1].0 != "ou" || parts[1].1 != "groups" || parts[0].0 != "cn" {
|
if parts[1].0 != "ou" || parts[1].1 != "groups" || parts[0].0 != "cn" {
|
||||||
bail!(
|
bail!(
|
||||||
r#"Unexpected group DN format. Expected: "cn=groupname,ou=groups,{}""#,
|
r#"Unexpected group DN format. Got "{}", expected: "cn=groupname,ou=groups,{}""#,
|
||||||
|
dn,
|
||||||
base_dn_str
|
base_dn_str
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(parts[0].1.to_string())
|
Ok(parts[0].1.to_string())
|
||||||
} else {
|
} else {
|
||||||
bail!(
|
bail!(
|
||||||
r#"Unexpected group DN format. Expected: "cn=groupname,ou=groups,{}""#,
|
r#"Unexpected group DN format. Got "{}", expected: "cn=groupname,ou=groups,{}""#,
|
||||||
|
dn,
|
||||||
base_dn_str
|
base_dn_str
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -58,62 +65,94 @@ fn get_user_id_from_distinguished_name(
|
|||||||
dn: &str,
|
dn: &str,
|
||||||
base_tree: &[(String, String)],
|
base_tree: &[(String, String)],
|
||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
ldap_user_dn: &str,
|
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let parts = parse_distinguished_name(dn)?;
|
let parts = parse_distinguished_name(dn)?;
|
||||||
if !is_subtree(&parts, base_tree) {
|
if !is_subtree(&parts, base_tree) {
|
||||||
bail!("Not a subtree of the base tree");
|
bail!("Not a subtree of the base tree");
|
||||||
}
|
}
|
||||||
if parts.len() == base_tree.len() + 1 {
|
if parts.len() == base_tree.len() + 2 {
|
||||||
if dn != ldap_user_dn {
|
|
||||||
bail!(r#"Wrong admin DN. Expected: "{}""#, ldap_user_dn);
|
|
||||||
}
|
|
||||||
Ok(parts[0].1.to_string())
|
|
||||||
} else if parts.len() == base_tree.len() + 2 {
|
|
||||||
if parts[1].0 != "ou" || parts[1].1 != "people" || parts[0].0 != "cn" {
|
if parts[1].0 != "ou" || parts[1].1 != "people" || parts[0].0 != "cn" {
|
||||||
bail!(
|
bail!(
|
||||||
r#"Unexpected user DN format. Expected: "cn=username,ou=people,{}""#,
|
r#"Unexpected user DN format. Got "{}", expected: "cn=username,ou=people,{}""#,
|
||||||
|
dn,
|
||||||
base_dn_str
|
base_dn_str
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(parts[0].1.to_string())
|
Ok(parts[0].1.to_string())
|
||||||
} else {
|
} else {
|
||||||
bail!(
|
bail!(
|
||||||
r#"Unexpected user DN format. Expected: "cn=username,ou=people,{}""#,
|
r#"Unexpected user DN format. Got "{}", expected: "cn=username,ou=people,{}""#,
|
||||||
|
dn,
|
||||||
base_dn_str
|
base_dn_str
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_attribute(user: &User, attribute: &str) -> Result<Vec<String>> {
|
fn get_user_attribute(user: &User, attribute: &str) -> Result<Vec<String>> {
|
||||||
match attribute {
|
match attribute {
|
||||||
"objectClass" => Ok(vec![
|
"objectClass" => Ok(vec![
|
||||||
"inetOrgPerson".to_string(),
|
"inetOrgPerson".to_string(),
|
||||||
"posixAccount".to_string(),
|
"posixAccount".to_string(),
|
||||||
"mailAccount".to_string(),
|
"mailAccount".to_string(),
|
||||||
|
"person".to_string(),
|
||||||
]),
|
]),
|
||||||
"uid" => Ok(vec![user.user_id.clone()]),
|
"uid" => Ok(vec![user.user_id.clone()]),
|
||||||
"mail" => Ok(vec![user.email.clone()]),
|
"mail" => Ok(vec![user.email.clone()]),
|
||||||
"givenName" => Ok(vec![user.first_name.clone()]),
|
"givenName" => Ok(vec![user.first_name.clone()]),
|
||||||
"sn" => Ok(vec![user.last_name.clone()]),
|
"sn" => Ok(vec![user.last_name.clone()]),
|
||||||
"cn" => Ok(vec![user.display_name.clone()]),
|
"cn" => Ok(vec![user.display_name.clone()]),
|
||||||
_ => bail!("Unsupported attribute: {}", attribute),
|
"displayName" => Ok(vec![user.display_name.clone()]),
|
||||||
|
"supportedExtension" => Ok(vec![]),
|
||||||
|
_ => bail!("Unsupported user attribute: {}", attribute),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_ldap_search_result_entry(
|
fn make_ldap_search_user_result_entry(
|
||||||
user: User,
|
user: User,
|
||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
attributes: &[String],
|
attributes: &[String],
|
||||||
) -> Result<LdapSearchResultEntry> {
|
) -> Result<LdapSearchResultEntry> {
|
||||||
Ok(LdapSearchResultEntry {
|
Ok(LdapSearchResultEntry {
|
||||||
dn: format!("cn={},{}", user.user_id, base_dn_str),
|
dn: format!("cn={},ou=people,{}", user.user_id, base_dn_str),
|
||||||
attributes: attributes
|
attributes: attributes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| {
|
.map(|a| {
|
||||||
Ok(LdapPartialAttribute {
|
Ok(LdapPartialAttribute {
|
||||||
atype: a.to_string(),
|
atype: a.to_string(),
|
||||||
vals: get_attribute(&user, a)?,
|
vals: get_user_attribute(&user, a)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<LdapPartialAttribute>>>()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_group_attribute(group: &Group, base_dn_str: &str, attribute: &str) -> Result<Vec<String>> {
|
||||||
|
match attribute {
|
||||||
|
"objectClass" => Ok(vec!["groupOfUniqueNames".to_string()]),
|
||||||
|
"cn" => Ok(vec![group.display_name.clone()]),
|
||||||
|
"uniqueMember" => Ok(group
|
||||||
|
.users
|
||||||
|
.iter()
|
||||||
|
.map(|u| format!("cn={},ou=people,{}", u, base_dn_str))
|
||||||
|
.collect()),
|
||||||
|
"supportedExtension" => Ok(vec![]),
|
||||||
|
_ => bail!("Unsupported group attribute: {}", attribute),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_ldap_search_group_result_entry(
|
||||||
|
group: Group,
|
||||||
|
base_dn_str: &str,
|
||||||
|
attributes: &[String],
|
||||||
|
) -> Result<LdapSearchResultEntry> {
|
||||||
|
Ok(LdapSearchResultEntry {
|
||||||
|
dn: format!("cn={},ou=groups,{}", group.display_name, base_dn_str),
|
||||||
|
attributes: attributes
|
||||||
|
.iter()
|
||||||
|
.map(|a| {
|
||||||
|
Ok(LdapPartialAttribute {
|
||||||
|
atype: a.to_string(),
|
||||||
|
vals: get_group_attribute(&group, base_dn_str, a)?,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<LdapPartialAttribute>>>()?,
|
.collect::<Result<Vec<LdapPartialAttribute>>>()?,
|
||||||
@ -138,7 +177,7 @@ fn map_field(field: &str) -> Result<String> {
|
|||||||
"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" {
|
} else if field == "cn" || field == "displayName" {
|
||||||
"display_name".to_string()
|
"display_name".to_string()
|
||||||
} else if field == "givenName" {
|
} else if field == "givenName" {
|
||||||
"first_name".to_string()
|
"first_name".to_string()
|
||||||
@ -172,18 +211,15 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
|
|||||||
ldap_base_dn
|
ldap_base_dn
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
ldap_user_dn: format!("cn={},{}", ldap_user_dn, &ldap_base_dn),
|
ldap_user_dn: format!("cn={},ou=people,{}", ldap_user_dn, &ldap_base_dn),
|
||||||
base_dn_str: ldap_base_dn,
|
base_dn_str: ldap_base_dn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg {
|
pub async fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg {
|
||||||
let user_id = match get_user_id_from_distinguished_name(
|
info!(r#"Received bind request for "{}""#, &sbr.dn);
|
||||||
&sbr.dn,
|
let user_id =
|
||||||
&self.base_dn,
|
match get_user_id_from_distinguished_name(&sbr.dn, &self.base_dn, &self.base_dn_str) {
|
||||||
&self.base_dn_str,
|
|
||||||
&self.ldap_user_dn,
|
|
||||||
) {
|
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return sbr.gen_error(LdapResultCode::NamingViolation, e.to_string()),
|
Err(e) => return sbr.gen_error(LdapResultCode::NamingViolation, e.to_string()),
|
||||||
};
|
};
|
||||||
@ -204,13 +240,20 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn do_search(&mut self, lsr: &SearchRequest) -> Vec<LdapMsg> {
|
pub async fn do_search(&mut self, lsr: &SearchRequest) -> Vec<LdapMsg> {
|
||||||
|
info!("Received search request with filters: {:?}", &lsr.filter);
|
||||||
if self.dn != self.ldap_user_dn {
|
if self.dn != self.ldap_user_dn {
|
||||||
return vec![lsr.gen_error(
|
return vec![lsr.gen_error(
|
||||||
LdapResultCode::InsufficentAccessRights,
|
LdapResultCode::InsufficentAccessRights,
|
||||||
r#"Current user is not allowed to query LDAP"#.to_string(),
|
format!(
|
||||||
|
r#"Current user `{}` is not allowed to query LDAP, expected {}"#,
|
||||||
|
&self.dn, &self.ldap_user_dn
|
||||||
|
),
|
||||||
)];
|
)];
|
||||||
}
|
}
|
||||||
let dn_parts = match parse_distinguished_name(&lsr.base) {
|
let dn_parts = if lsr.base.is_empty() {
|
||||||
|
self.base_dn.clone()
|
||||||
|
} else {
|
||||||
|
match parse_distinguished_name(&lsr.base) {
|
||||||
Ok(dn) => dn,
|
Ok(dn) => dn,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return vec![lsr.gen_error(
|
return vec![lsr.gen_error(
|
||||||
@ -218,17 +261,34 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
|
|||||||
format!(r#"Could not parse base DN: "{}""#, lsr.base),
|
format!(r#"Could not parse base DN: "{}""#, lsr.base),
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if !is_subtree(&dn_parts, &self.base_dn) {
|
if !is_subtree(&dn_parts, &self.base_dn) {
|
||||||
// Search path is not in our tree, just return an empty success.
|
// Search path is not in our tree, just return an empty success.
|
||||||
return vec![lsr.gen_success()];
|
return vec![lsr.gen_success()];
|
||||||
}
|
}
|
||||||
let filters = match self.convert_filter(&lsr.filter) {
|
let mut results = Vec::new();
|
||||||
|
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()))
|
||||||
|
{
|
||||||
|
results.extend(self.get_user_list(lsr).await);
|
||||||
|
}
|
||||||
|
if dn_parts.len() == self.base_dn.len() + 1
|
||||||
|
&& dn_parts[0] == ("ou".to_string(), "groups".to_string())
|
||||||
|
{
|
||||||
|
results.extend(self.get_groups_list(lsr).await);
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user_list(&self, lsr: &SearchRequest) -> Vec<LdapMsg> {
|
||||||
|
let filters = match self.convert_user_filter(&lsr.filter) {
|
||||||
Ok(f) => Some(f),
|
Ok(f) => Some(f),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return vec![lsr.gen_error(
|
return vec![lsr.gen_error(
|
||||||
LdapResultCode::UnwillingToPerform,
|
LdapResultCode::UnwillingToPerform,
|
||||||
format!("Unsupported filter: {}", e),
|
format!("Unsupported user filter: {}", e),
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -237,14 +297,84 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
return vec![lsr.gen_error(
|
return vec![lsr.gen_error(
|
||||||
LdapResultCode::Other,
|
LdapResultCode::Other,
|
||||||
format!(r#"Error during search for "{}": {}"#, lsr.base, e),
|
format!(r#"Error during searching user "{}": {}"#, lsr.base, e),
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
users
|
users
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|u| make_ldap_search_result_entry(u, &self.base_dn_str, &lsr.attrs))
|
.map(|u| make_ldap_search_user_result_entry(u, &self.base_dn_str, &lsr.attrs))
|
||||||
|
.map(|entry| Ok(lsr.gen_result_entry(entry?)))
|
||||||
|
// If the processing succeeds, add a success message at the end.
|
||||||
|
.chain(std::iter::once(Ok(lsr.gen_success())))
|
||||||
|
.collect::<Result<Vec<_>>>()
|
||||||
|
.unwrap_or_else(|e| vec![lsr.gen_error(LdapResultCode::NoSuchAttribute, e.to_string())])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_groups_list(&self, lsr: &SearchRequest) -> Vec<LdapMsg> {
|
||||||
|
let for_user = match self.get_group_filter(&lsr.filter) {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
return vec![lsr.gen_error(
|
||||||
|
LdapResultCode::UnwillingToPerform,
|
||||||
|
format!("Unsupported group filter: {}", e),
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn get_users_for_group<Backend: BackendHandler>(
|
||||||
|
backend_handler: &Backend,
|
||||||
|
g: &GroupIdAndName,
|
||||||
|
) -> Result<Group> {
|
||||||
|
let users = backend_handler
|
||||||
|
.list_users(Some(RequestFilter::MemberOfId(g.0)))
|
||||||
|
.await?;
|
||||||
|
Ok(Group {
|
||||||
|
id: g.0,
|
||||||
|
display_name: g.1.clone(),
|
||||||
|
users: users.into_iter().map(|u| u.user_id).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let groups: Vec<Group> = if let Some(user) = for_user {
|
||||||
|
let groups_without_users = match self.backend_handler.get_user_groups(&user).await {
|
||||||
|
Ok(groups) => groups,
|
||||||
|
Err(e) => {
|
||||||
|
return vec![lsr.gen_error(
|
||||||
|
LdapResultCode::Other,
|
||||||
|
format!(r#"Error while listing user groups: "{}": {}"#, lsr.base, e),
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match tokio_stream::iter(groups_without_users.iter())
|
||||||
|
.then(|g| async move { get_users_for_group::<Backend>(&self.backend_handler, g).await })
|
||||||
|
.try_collect::<Vec<Group>>()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(groups) => groups,
|
||||||
|
Err(e) => {
|
||||||
|
return vec![lsr.gen_error(
|
||||||
|
LdapResultCode::Other,
|
||||||
|
format!(r#"Error while listing user groups: "{}": {}"#, lsr.base, e),
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.backend_handler.list_groups().await {
|
||||||
|
Ok(groups) => groups,
|
||||||
|
Err(e) => {
|
||||||
|
return vec![lsr.gen_error(
|
||||||
|
LdapResultCode::Other,
|
||||||
|
format!(r#"Error while listing groups "{}": {}"#, lsr.base, e),
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
groups
|
||||||
|
.into_iter()
|
||||||
|
.map(|u| make_ldap_search_group_result_entry(u, &self.base_dn_str, &lsr.attrs))
|
||||||
.map(|entry| Ok(lsr.gen_result_entry(entry?)))
|
.map(|entry| Ok(lsr.gen_result_entry(entry?)))
|
||||||
// If the processing succeeds, add a success message at the end.
|
// If the processing succeeds, add a success message at the end.
|
||||||
.chain(std::iter::once(Ok(lsr.gen_success())))
|
.chain(std::iter::once(Ok(lsr.gen_success())))
|
||||||
@ -261,7 +391,7 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_ldap_message(&mut self, server_op: ServerOps) -> Option<Vec<LdapMsg>> {
|
pub async fn handle_ldap_message(&mut self, server_op: ServerOps) -> Option<Vec<LdapMsg>> {
|
||||||
let result = match server_op {
|
Some(match server_op {
|
||||||
ServerOps::SimpleBind(sbr) => vec![self.do_bind(&sbr).await],
|
ServerOps::SimpleBind(sbr) => vec![self.do_bind(&sbr).await],
|
||||||
ServerOps::Search(sr) => self.do_search(&sr).await,
|
ServerOps::Search(sr) => self.do_search(&sr).await,
|
||||||
ServerOps::Unbind(_) => {
|
ServerOps::Unbind(_) => {
|
||||||
@ -269,27 +399,46 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
ServerOps::Whoami(wr) => vec![self.do_whoami(&wr)],
|
ServerOps::Whoami(wr) => vec![self.do_whoami(&wr)],
|
||||||
};
|
})
|
||||||
Some(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_filter(&self, filter: &LdapFilter) -> Result<RequestFilter> {
|
fn get_group_filter(&self, filter: &LdapFilter) -> Result<Option<String>> {
|
||||||
|
match filter {
|
||||||
|
LdapFilter::Equality(field, value) => {
|
||||||
|
if field == "member" || field == "uniqueMember" {
|
||||||
|
let user_name = get_user_id_from_distinguished_name(
|
||||||
|
value,
|
||||||
|
&self.base_dn,
|
||||||
|
&self.base_dn_str,
|
||||||
|
)?;
|
||||||
|
Ok(Some(user_name))
|
||||||
|
} else if field == "objectClass" && value == "groupOfUniqueNames" {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
bail!("Unsupported group filter: {:?}", filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => bail!("Unsupported group filter: {:?}", filter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_user_filter(&self, filter: &LdapFilter) -> Result<RequestFilter> {
|
||||||
match filter {
|
match filter {
|
||||||
LdapFilter::And(filters) => Ok(RequestFilter::And(
|
LdapFilter::And(filters) => Ok(RequestFilter::And(
|
||||||
filters
|
filters
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| self.convert_filter(f))
|
.map(|f| self.convert_user_filter(f))
|
||||||
.collect::<Result<_>>()?,
|
.collect::<Result<_>>()?,
|
||||||
)),
|
)),
|
||||||
LdapFilter::Or(filters) => Ok(RequestFilter::Or(
|
LdapFilter::Or(filters) => Ok(RequestFilter::Or(
|
||||||
filters
|
filters
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| self.convert_filter(f))
|
.map(|f| self.convert_user_filter(f))
|
||||||
.collect::<Result<_>>()?,
|
.collect::<Result<_>>()?,
|
||||||
)),
|
)),
|
||||||
LdapFilter::Not(filter) => {
|
LdapFilter::Not(filter) => Ok(RequestFilter::Not(Box::new(
|
||||||
Ok(RequestFilter::Not(Box::new(self.convert_filter(&*filter)?)))
|
self.convert_user_filter(&*filter)?,
|
||||||
}
|
))),
|
||||||
LdapFilter::Equality(field, value) => {
|
LdapFilter::Equality(field, value) => {
|
||||||
if field == "memberOf" {
|
if field == "memberOf" {
|
||||||
let group_name = get_group_id_from_distinguished_name(
|
let group_name = get_group_id_from_distinguished_name(
|
||||||
@ -298,19 +447,29 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
|
|||||||
&self.base_dn_str,
|
&self.base_dn_str,
|
||||||
)?;
|
)?;
|
||||||
Ok(RequestFilter::MemberOf(group_name))
|
Ok(RequestFilter::MemberOf(group_name))
|
||||||
|
} else if field == "objectClass" {
|
||||||
|
if value == "person"
|
||||||
|
|| value == "inetOrgPerson"
|
||||||
|
|| value == "posixAccount"
|
||||||
|
|| value == "mailAccount"
|
||||||
|
{
|
||||||
|
Ok(RequestFilter::And(vec![]))
|
||||||
|
} else {
|
||||||
|
Ok(RequestFilter::Not(Box::new(RequestFilter::And(vec![]))))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(RequestFilter::Equality(map_field(field)?, value.clone()))
|
Ok(RequestFilter::Equality(map_field(field)?, value.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LdapFilter::Present(field) => {
|
LdapFilter::Present(field) => {
|
||||||
// Check that it's a field we support.
|
// Check that it's a field we support.
|
||||||
if field == "objectclass" || map_field(field).is_ok() {
|
if field == "objectClass" || map_field(field).is_ok() {
|
||||||
Ok(RequestFilter::And(Vec::new()))
|
Ok(RequestFilter::And(vec![]))
|
||||||
} else {
|
} else {
|
||||||
Ok(RequestFilter::Not(Box::new(RequestFilter::And(Vec::new()))))
|
Ok(RequestFilter::Not(Box::new(RequestFilter::And(vec![]))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => bail!("Unsupported filter: {:?}", filter),
|
_ => bail!("Unsupported user filter: {:?}", filter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -335,10 +494,10 @@ mod tests {
|
|||||||
LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
|
LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
|
||||||
let request = SimpleBindRequest {
|
let request = SimpleBindRequest {
|
||||||
msgid: 1,
|
msgid: 1,
|
||||||
dn: "cn=test,dc=example,dc=com".to_string(),
|
dn: "cn=test,ou=people,dc=example,dc=com".to_string(),
|
||||||
pw: "pass".to_string(),
|
pw: "pass".to_string(),
|
||||||
};
|
};
|
||||||
ldap_handler.do_bind(&request).await;
|
assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
|
||||||
ldap_handler
|
ldap_handler
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,7 +555,7 @@ mod tests {
|
|||||||
|
|
||||||
let request = SimpleBindRequest {
|
let request = SimpleBindRequest {
|
||||||
msgid: 2,
|
msgid: 2,
|
||||||
dn: "cn=test,dc=example,dc=com".to_string(),
|
dn: "cn=test,ou=people,dc=example,dc=com".to_string(),
|
||||||
pw: "pass".to_string(),
|
pw: "pass".to_string(),
|
||||||
};
|
};
|
||||||
assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
|
assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
|
||||||
@ -404,7 +563,7 @@ mod tests {
|
|||||||
let request = WhoamiRequest { msgid: 3 };
|
let request = WhoamiRequest { msgid: 3 };
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ldap_handler.do_whoami(&request),
|
ldap_handler.do_whoami(&request),
|
||||||
request.gen_success("dn: cn=test,dc=example,dc=com")
|
request.gen_success("dn: cn=test,ou=people,dc=example,dc=com")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,7 +610,7 @@ mod tests {
|
|||||||
ldap_handler.do_search(&request).await,
|
ldap_handler.do_search(&request).await,
|
||||||
vec![request.gen_error(
|
vec![request.gen_error(
|
||||||
LdapResultCode::InsufficentAccessRights,
|
LdapResultCode::InsufficentAccessRights,
|
||||||
r#"Current user is not allowed to query LDAP"#.to_string()
|
r#"Current user `cn=test,ou=people,dc=example,dc=com` is not allowed to query LDAP, expected cn=admin,ou=people,dc=example,dc=com"#.to_string()
|
||||||
)]
|
)]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -471,7 +630,7 @@ mod tests {
|
|||||||
ldap_handler.do_bind(&request).await,
|
ldap_handler.do_bind(&request).await,
|
||||||
request.gen_error(
|
request.gen_error(
|
||||||
LdapResultCode::NamingViolation,
|
LdapResultCode::NamingViolation,
|
||||||
r#"Wrong admin DN. Expected: "cn=admin,dc=example,dc=com""#.to_string()
|
r#"Unexpected user DN format. Got "cn=bob,dc=example,dc=com", expected: "cn=username,ou=people,dc=example,dc=com""#.to_string()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
let request = SimpleBindRequest {
|
let request = SimpleBindRequest {
|
||||||
@ -483,7 +642,7 @@ mod tests {
|
|||||||
ldap_handler.do_bind(&request).await,
|
ldap_handler.do_bind(&request).await,
|
||||||
request.gen_error(
|
request.gen_error(
|
||||||
LdapResultCode::NamingViolation,
|
LdapResultCode::NamingViolation,
|
||||||
r#"Unexpected user DN format. Expected: "cn=username,ou=people,dc=example,dc=com""#
|
r#"Unexpected user DN format. Got "cn=bob,ou=groups,dc=example,dc=com", expected: "cn=username,ou=people,dc=example,dc=com""#
|
||||||
.to_string()
|
.to_string()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -559,14 +718,15 @@ mod tests {
|
|||||||
ldap_handler.do_search(&request).await,
|
ldap_handler.do_search(&request).await,
|
||||||
vec![
|
vec![
|
||||||
request.gen_result_entry(LdapSearchResultEntry {
|
request.gen_result_entry(LdapSearchResultEntry {
|
||||||
dn: "cn=bob_1,dc=example,dc=com".to_string(),
|
dn: "cn=bob_1,ou=people,dc=example,dc=com".to_string(),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"inetOrgPerson".to_string(),
|
"inetOrgPerson".to_string(),
|
||||||
"posixAccount".to_string(),
|
"posixAccount".to_string(),
|
||||||
"mailAccount".to_string()
|
"mailAccount".to_string(),
|
||||||
|
"person".to_string()
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
@ -592,14 +752,15 @@ mod tests {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
request.gen_result_entry(LdapSearchResultEntry {
|
request.gen_result_entry(LdapSearchResultEntry {
|
||||||
dn: "cn=jim,dc=example,dc=com".to_string(),
|
dn: "cn=jim,ou=people,dc=example,dc=com".to_string(),
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
atype: "objectClass".to_string(),
|
atype: "objectClass".to_string(),
|
||||||
vals: vec![
|
vals: vec![
|
||||||
"inetOrgPerson".to_string(),
|
"inetOrgPerson".to_string(),
|
||||||
"posixAccount".to_string(),
|
"posixAccount".to_string(),
|
||||||
"mailAccount".to_string()
|
"mailAccount".to_string(),
|
||||||
|
"person".to_string()
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
LdapPartialAttribute {
|
LdapPartialAttribute {
|
||||||
@ -674,7 +835,7 @@ mod tests {
|
|||||||
ldap_handler.do_search(&request).await,
|
ldap_handler.do_search(&request).await,
|
||||||
vec![request.gen_error(
|
vec![request.gen_error(
|
||||||
LdapResultCode::UnwillingToPerform,
|
LdapResultCode::UnwillingToPerform,
|
||||||
"Unsupported filter: Unsupported filter: Substring(\"uid\", LdapSubstringFilter { initial: None, any: [], final_: None })".to_string()
|
"Unsupported user filter: Unsupported user filter: Substring(\"uid\", LdapSubstringFilter { initial: None, any: [], final_: None })".to_string()
|
||||||
)]
|
)]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@ where
|
|||||||
{
|
{
|
||||||
use futures_util::SinkExt;
|
use futures_util::SinkExt;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
let server_op = match msg.map_err(|_e| ()).and_then(ServerOps::try_from) {
|
let server_op = match msg
|
||||||
|
.map_err(|e| warn!("Error while receiving LDAP op: {:#}", e))
|
||||||
|
.and_then(ServerOps::try_from)
|
||||||
|
{
|
||||||
Ok(a_value) => a_value,
|
Ok(a_value) => a_value,
|
||||||
Err(an_error) => {
|
Err(an_error) => {
|
||||||
let _err = resp
|
let _err = resp
|
||||||
|
Loading…
Reference in New Issue
Block a user