mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
server: refactor ldap_handler
Split it into several files, move them into the domain folder, introduce `LdapError` for better control flow.
This commit is contained in:
parent
0be440efc8
commit
35aa656677
17
server/src/domain/ldap/error.rs
Normal file
17
server/src/domain/ldap/error.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use ldap3_proto::LdapResultCode;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LdapError {
|
||||
pub code: LdapResultCode,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LdapError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LdapError {}
|
||||
|
||||
pub type LdapResult<T> = std::result::Result<T, LdapError>;
|
218
server/src/domain/ldap/group.rs
Normal file
218
server/src/domain/ldap/group.rs
Normal file
@ -0,0 +1,218 @@
|
||||
use ldap3_proto::{
|
||||
proto::LdapOp, LdapFilter, LdapPartialAttribute, LdapResultCode, LdapSearchResultEntry,
|
||||
};
|
||||
use tracing::{debug, info, instrument, warn};
|
||||
|
||||
use crate::domain::{
|
||||
handler::{BackendHandler, Group, GroupRequestFilter, UserId, Uuid},
|
||||
ldap::error::LdapError,
|
||||
};
|
||||
|
||||
use super::{
|
||||
error::LdapResult,
|
||||
utils::{expand_attribute_wildcards, get_user_id_from_distinguished_name, map_field, LdapInfo},
|
||||
};
|
||||
|
||||
fn get_group_attribute(
|
||||
group: &Group,
|
||||
base_dn_str: &str,
|
||||
attribute: &str,
|
||||
user_filter: &Option<&UserId>,
|
||||
ignored_group_attributes: &[String],
|
||||
) -> Option<Vec<Vec<u8>>> {
|
||||
let attribute = attribute.to_ascii_lowercase();
|
||||
let attribute_values = match attribute.as_str() {
|
||||
"objectclass" => vec![b"groupOfUniqueNames".to_vec()],
|
||||
// Always returned as part of the base response.
|
||||
"dn" | "distinguishedname" => return None,
|
||||
"cn" | "uid" => vec![group.display_name.clone().into_bytes()],
|
||||
"entryuuid" => vec![group.uuid.to_string().into_bytes()],
|
||||
"member" | "uniquemember" => group
|
||||
.users
|
||||
.iter()
|
||||
.filter(|u| user_filter.map(|f| *u == f).unwrap_or(true))
|
||||
.map(|u| format!("uid={},ou=people,{}", u, base_dn_str).into_bytes())
|
||||
.collect(),
|
||||
"1.1" => return None,
|
||||
// We ignore the operational attribute wildcard
|
||||
"+" => return None,
|
||||
"*" => {
|
||||
panic!(
|
||||
"Matched {}, * should have been expanded into attribute list and * removed",
|
||||
attribute
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
if !ignored_group_attributes.contains(&attribute) {
|
||||
warn!(
|
||||
r#"Ignoring unrecognized group attribute: {}\n\
|
||||
To disable this warning, add it to "ignored_group_attributes" in the config."#,
|
||||
attribute
|
||||
);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if attribute_values.len() == 1 && attribute_values[0].is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(attribute_values)
|
||||
}
|
||||
}
|
||||
|
||||
const ALL_GROUP_ATTRIBUTE_KEYS: &[&str] = &[
|
||||
"objectclass",
|
||||
"uid",
|
||||
"cn",
|
||||
"member",
|
||||
"uniquemember",
|
||||
"entryuuid",
|
||||
];
|
||||
|
||||
fn make_ldap_search_group_result_entry(
|
||||
group: Group,
|
||||
base_dn_str: &str,
|
||||
attributes: &[String],
|
||||
user_filter: &Option<&UserId>,
|
||||
ignored_group_attributes: &[String],
|
||||
) -> LdapSearchResultEntry {
|
||||
let expanded_attributes = expand_attribute_wildcards(attributes, ALL_GROUP_ATTRIBUTE_KEYS);
|
||||
|
||||
LdapSearchResultEntry {
|
||||
dn: format!("cn={},ou=groups,{}", group.display_name, base_dn_str),
|
||||
attributes: expanded_attributes
|
||||
.iter()
|
||||
.filter_map(|a| {
|
||||
let values = get_group_attribute(
|
||||
&group,
|
||||
base_dn_str,
|
||||
a,
|
||||
user_filter,
|
||||
ignored_group_attributes,
|
||||
)?;
|
||||
Some(LdapPartialAttribute {
|
||||
atype: a.to_string(),
|
||||
vals: values,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<LdapPartialAttribute>>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_group_filter(
|
||||
ldap_info: &LdapInfo,
|
||||
filter: &LdapFilter,
|
||||
) -> LdapResult<GroupRequestFilter> {
|
||||
let rec = |f| convert_group_filter(ldap_info, f);
|
||||
match filter {
|
||||
LdapFilter::Equality(field, value) => {
|
||||
let field = &field.to_ascii_lowercase();
|
||||
let value = &value.to_ascii_lowercase();
|
||||
match field.as_str() {
|
||||
"member" | "uniquemember" => {
|
||||
let user_name = get_user_id_from_distinguished_name(
|
||||
value,
|
||||
&ldap_info.base_dn,
|
||||
&ldap_info.base_dn_str,
|
||||
)?;
|
||||
Ok(GroupRequestFilter::Member(user_name))
|
||||
}
|
||||
"objectclass" => match value.as_str() {
|
||||
"groupofuniquenames" | "groupofnames" => Ok(GroupRequestFilter::And(vec![])),
|
||||
_ => Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
|
||||
vec![],
|
||||
)))),
|
||||
},
|
||||
_ => match map_field(field) {
|
||||
Some("display_name") | Some("user_id") => {
|
||||
Ok(GroupRequestFilter::DisplayName(value.to_string()))
|
||||
}
|
||||
Some("uuid") => Ok(GroupRequestFilter::Uuid(
|
||||
Uuid::try_from(value.as_str()).map_err(|e| LdapError {
|
||||
code: LdapResultCode::InappropriateMatching,
|
||||
message: format!("Invalid UUID: {:#}", e),
|
||||
})?,
|
||||
)),
|
||||
_ => {
|
||||
if !ldap_info.ignored_group_attributes.contains(field) {
|
||||
warn!(
|
||||
r#"Ignoring unknown group attribute "{:?}" in filter.\n\
|
||||
To disable this warning, add it to "ignored_group_attributes" in the config."#,
|
||||
field
|
||||
);
|
||||
}
|
||||
Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
|
||||
vec![],
|
||||
))))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
LdapFilter::And(filters) => Ok(GroupRequestFilter::And(
|
||||
filters.iter().map(rec).collect::<LdapResult<_>>()?,
|
||||
)),
|
||||
LdapFilter::Or(filters) => Ok(GroupRequestFilter::Or(
|
||||
filters.iter().map(rec).collect::<LdapResult<_>>()?,
|
||||
)),
|
||||
LdapFilter::Not(filter) => Ok(GroupRequestFilter::Not(Box::new(rec(filter)?))),
|
||||
LdapFilter::Present(field) => {
|
||||
let field = &field.to_ascii_lowercase();
|
||||
if field == "objectclass"
|
||||
|| field == "dn"
|
||||
|| field == "distinguishedname"
|
||||
|| ALL_GROUP_ATTRIBUTE_KEYS.contains(&field.as_str())
|
||||
{
|
||||
Ok(GroupRequestFilter::And(vec![]))
|
||||
} else {
|
||||
Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
|
||||
vec![],
|
||||
))))
|
||||
}
|
||||
}
|
||||
_ => Err(LdapError {
|
||||
code: LdapResultCode::UnwillingToPerform,
|
||||
message: format!("Unsupported group filter: {:?}", filter),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
pub async fn get_groups_list<Backend: BackendHandler>(
|
||||
ldap_info: &LdapInfo,
|
||||
ldap_filter: &LdapFilter,
|
||||
attributes: &[String],
|
||||
base: &str,
|
||||
user_filter: &Option<&UserId>,
|
||||
backend: &mut Backend,
|
||||
) -> LdapResult<Vec<LdapOp>> {
|
||||
debug!(?ldap_filter);
|
||||
let filter = convert_group_filter(ldap_info, ldap_filter)?;
|
||||
let parsed_filters = match user_filter {
|
||||
None => filter,
|
||||
Some(u) => {
|
||||
info!("Unprivileged search, limiting results");
|
||||
GroupRequestFilter::And(vec![filter, GroupRequestFilter::Member((*u).clone())])
|
||||
}
|
||||
};
|
||||
debug!(?parsed_filters);
|
||||
let groups = backend
|
||||
.list_groups(Some(parsed_filters))
|
||||
.await
|
||||
.map_err(|e| LdapError {
|
||||
code: LdapResultCode::Other,
|
||||
message: format!(r#"Error while listing groups "{}": {:#}"#, base, e),
|
||||
})?;
|
||||
|
||||
Ok(groups
|
||||
.into_iter()
|
||||
.map(|u| {
|
||||
LdapOp::SearchResultEntry(make_ldap_search_group_result_entry(
|
||||
u,
|
||||
&ldap_info.base_dn_str,
|
||||
attributes,
|
||||
user_filter,
|
||||
&ldap_info.ignored_group_attributes,
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>())
|
||||
}
|
4
server/src/domain/ldap/mod.rs
Normal file
4
server/src/domain/ldap/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod error;
|
||||
pub mod group;
|
||||
pub mod user;
|
||||
pub mod utils;
|
236
server/src/domain/ldap/user.rs
Normal file
236
server/src/domain/ldap/user.rs
Normal file
@ -0,0 +1,236 @@
|
||||
use ldap3_proto::{
|
||||
proto::LdapOp, LdapFilter, LdapPartialAttribute, LdapResultCode, LdapSearchResultEntry,
|
||||
};
|
||||
use tracing::{debug, info, instrument, warn};
|
||||
|
||||
use crate::domain::{
|
||||
handler::{BackendHandler, GroupDetails, User, UserId, UserRequestFilter},
|
||||
ldap::{error::LdapError, utils::expand_attribute_wildcards},
|
||||
};
|
||||
|
||||
use super::{
|
||||
error::LdapResult,
|
||||
utils::{get_group_id_from_distinguished_name, map_field, LdapInfo},
|
||||
};
|
||||
|
||||
fn get_user_attribute(
|
||||
user: &User,
|
||||
attribute: &str,
|
||||
base_dn_str: &str,
|
||||
groups: Option<&[GroupDetails]>,
|
||||
ignored_user_attributes: &[String],
|
||||
) -> Option<Vec<Vec<u8>>> {
|
||||
let attribute = attribute.to_ascii_lowercase();
|
||||
let attribute_values = match attribute.as_str() {
|
||||
"objectclass" => vec![
|
||||
b"inetOrgPerson".to_vec(),
|
||||
b"posixAccount".to_vec(),
|
||||
b"mailAccount".to_vec(),
|
||||
b"person".to_vec(),
|
||||
],
|
||||
// dn is always returned as part of the base response.
|
||||
"dn" | "distinguishedname" => return None,
|
||||
"uid" => vec![user.user_id.to_string().into_bytes()],
|
||||
"entryuuid" => vec![user.uuid.to_string().into_bytes()],
|
||||
"mail" => vec![user.email.clone().into_bytes()],
|
||||
"givenname" => vec![user.first_name.clone().into_bytes()],
|
||||
"sn" => vec![user.last_name.clone().into_bytes()],
|
||||
"jpegphoto" => vec![user.avatar.clone().into_bytes()],
|
||||
"memberof" => groups
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|id_and_name| {
|
||||
format!(
|
||||
"uid={},ou=groups,{}",
|
||||
&id_and_name.display_name, base_dn_str
|
||||
)
|
||||
.into_bytes()
|
||||
})
|
||||
.collect(),
|
||||
"cn" | "displayname" => vec![user.display_name.clone().into_bytes()],
|
||||
"createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339().into_bytes()],
|
||||
"1.1" => return None,
|
||||
// We ignore the operational attribute wildcard.
|
||||
"+" => return None,
|
||||
"*" => {
|
||||
panic!(
|
||||
"Matched {}, * should have been expanded into attribute list and * removed",
|
||||
attribute
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
if !ignored_user_attributes.contains(&attribute) {
|
||||
warn!(
|
||||
r#"Ignoring unrecognized group attribute: {}\n\
|
||||
To disable this warning, add it to "ignored_user_attributes" in the config."#,
|
||||
attribute
|
||||
);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if attribute_values.len() == 1 && attribute_values[0].is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(attribute_values)
|
||||
}
|
||||
}
|
||||
|
||||
const ALL_USER_ATTRIBUTE_KEYS: &[&str] = &[
|
||||
"objectclass",
|
||||
"uid",
|
||||
"mail",
|
||||
"givenname",
|
||||
"sn",
|
||||
"cn",
|
||||
"jpegPhoto",
|
||||
"createtimestamp",
|
||||
"entryuuid",
|
||||
];
|
||||
|
||||
fn make_ldap_search_user_result_entry(
|
||||
user: User,
|
||||
base_dn_str: &str,
|
||||
attributes: &[&str],
|
||||
groups: Option<&[GroupDetails]>,
|
||||
ignored_user_attributes: &[String],
|
||||
) -> LdapSearchResultEntry {
|
||||
let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
|
||||
|
||||
LdapSearchResultEntry {
|
||||
dn,
|
||||
attributes: attributes
|
||||
.iter()
|
||||
.filter_map(|a| {
|
||||
let values =
|
||||
get_user_attribute(&user, a, base_dn_str, groups, ignored_user_attributes)?;
|
||||
Some(LdapPartialAttribute {
|
||||
atype: a.to_string(),
|
||||
vals: values,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<LdapPartialAttribute>>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<UserRequestFilter> {
|
||||
let rec = |f| convert_user_filter(ldap_info, f);
|
||||
match filter {
|
||||
LdapFilter::And(filters) => Ok(UserRequestFilter::And(
|
||||
filters.iter().map(rec).collect::<LdapResult<_>>()?,
|
||||
)),
|
||||
LdapFilter::Or(filters) => Ok(UserRequestFilter::Or(
|
||||
filters.iter().map(rec).collect::<LdapResult<_>>()?,
|
||||
)),
|
||||
LdapFilter::Not(filter) => Ok(UserRequestFilter::Not(Box::new(rec(filter)?))),
|
||||
LdapFilter::Equality(field, value) => {
|
||||
let field = &field.to_ascii_lowercase();
|
||||
match field.as_str() {
|
||||
"memberof" => {
|
||||
let group_name = get_group_id_from_distinguished_name(
|
||||
&value.to_ascii_lowercase(),
|
||||
&ldap_info.base_dn,
|
||||
&ldap_info.base_dn_str,
|
||||
)?;
|
||||
Ok(UserRequestFilter::MemberOf(group_name))
|
||||
}
|
||||
"objectclass" => match value.to_ascii_lowercase().as_str() {
|
||||
"person" | "inetorgperson" | "posixaccount" | "mailaccount" => {
|
||||
Ok(UserRequestFilter::And(vec![]))
|
||||
}
|
||||
_ => Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
|
||||
vec![],
|
||||
)))),
|
||||
},
|
||||
_ => match map_field(field) {
|
||||
Some(field) => {
|
||||
if field == "user_id" {
|
||||
Ok(UserRequestFilter::UserId(UserId::new(value)))
|
||||
} else {
|
||||
Ok(UserRequestFilter::Equality(
|
||||
field.to_string(),
|
||||
value.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if !ldap_info.ignored_user_attributes.contains(field) {
|
||||
warn!(
|
||||
r#"Ignoring unknown user attribute "{}" in filter.\n\
|
||||
To disable this warning, add it to "ignored_user_attributes" in the config"#,
|
||||
field
|
||||
);
|
||||
}
|
||||
Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
|
||||
vec![],
|
||||
))))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
LdapFilter::Present(field) => {
|
||||
let field = &field.to_ascii_lowercase();
|
||||
// Check that it's a field we support.
|
||||
if field == "objectclass"
|
||||
|| field == "dn"
|
||||
|| field == "distinguishedname"
|
||||
|| ALL_USER_ATTRIBUTE_KEYS.contains(&field.as_str())
|
||||
{
|
||||
Ok(UserRequestFilter::And(vec![]))
|
||||
} else {
|
||||
Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
|
||||
vec![],
|
||||
))))
|
||||
}
|
||||
}
|
||||
_ => Err(LdapError {
|
||||
code: LdapResultCode::UnwillingToPerform,
|
||||
message: format!("Unsupported user filter: {:?}", filter),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
pub async fn get_user_list<Backend: BackendHandler>(
|
||||
ldap_info: &LdapInfo,
|
||||
ldap_filter: &LdapFilter,
|
||||
attributes: &[String],
|
||||
base: &str,
|
||||
user_filter: &Option<&UserId>,
|
||||
backend: &mut Backend,
|
||||
) -> LdapResult<Vec<LdapOp>> {
|
||||
debug!(?ldap_filter);
|
||||
let filters = convert_user_filter(ldap_info, ldap_filter)?;
|
||||
let parsed_filters = match user_filter {
|
||||
None => filters,
|
||||
Some(u) => {
|
||||
info!("Unprivileged search, limiting results");
|
||||
UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
|
||||
}
|
||||
};
|
||||
debug!(?parsed_filters);
|
||||
let expanded_attributes = expand_attribute_wildcards(attributes, ALL_USER_ATTRIBUTE_KEYS);
|
||||
let need_groups = expanded_attributes
|
||||
.iter()
|
||||
.any(|s| s.to_ascii_lowercase() == "memberof");
|
||||
let users = backend
|
||||
.list_users(Some(parsed_filters), need_groups)
|
||||
.await
|
||||
.map_err(|e| LdapError {
|
||||
code: LdapResultCode::Other,
|
||||
message: format!(r#"Error while searching user "{}": {:#}"#, base, e),
|
||||
})?;
|
||||
|
||||
Ok(users
|
||||
.into_iter()
|
||||
.map(|u| {
|
||||
LdapOp::SearchResultEntry(make_ldap_search_user_result_entry(
|
||||
u.user,
|
||||
&ldap_info.base_dn_str,
|
||||
&expanded_attributes,
|
||||
u.groups.as_deref(),
|
||||
&ldap_info.ignored_user_attributes,
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>())
|
||||
}
|
157
server/src/domain/ldap/utils.rs
Normal file
157
server/src/domain/ldap/utils.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use itertools::Itertools;
|
||||
use ldap3_proto::LdapResultCode;
|
||||
use tracing::{debug, instrument, warn};
|
||||
|
||||
use crate::domain::handler::UserId;
|
||||
|
||||
use super::error::{LdapError, LdapResult};
|
||||
|
||||
fn make_dn_pair<I>(mut iter: I) -> LdapResult<(String, String)>
|
||||
where
|
||||
I: Iterator<Item = String>,
|
||||
{
|
||||
(|| {
|
||||
let pair = (
|
||||
iter.next().ok_or_else(|| "Empty DN element".to_string())?,
|
||||
iter.next().ok_or_else(|| "Missing DN value".to_string())?,
|
||||
);
|
||||
if let Some(e) = iter.next() {
|
||||
Err(format!(
|
||||
r#"Too many elements in distinguished name: "{:?}", "{:?}", "{:?}""#,
|
||||
pair.0, pair.1, e
|
||||
))
|
||||
} else {
|
||||
Ok(pair)
|
||||
}
|
||||
})()
|
||||
.map_err(|s| LdapError {
|
||||
code: LdapResultCode::InvalidDNSyntax,
|
||||
message: s,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_distinguished_name(dn: &str) -> LdapResult<Vec<(String, String)>> {
|
||||
assert!(dn == dn.to_ascii_lowercase());
|
||||
dn.split(',')
|
||||
.map(|s| make_dn_pair(s.split('=').map(str::trim).map(String::from)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_id_from_distinguished_name(
|
||||
dn: &str,
|
||||
base_tree: &[(String, String)],
|
||||
base_dn_str: &str,
|
||||
is_group: bool,
|
||||
) -> LdapResult<String> {
|
||||
let parts = parse_distinguished_name(dn)?;
|
||||
{
|
||||
let ou = if is_group { "groups" } else { "people" };
|
||||
if !is_subtree(&parts, base_tree) {
|
||||
Err("Not a subtree of the base tree".to_string())
|
||||
} else if parts.len() == base_tree.len() + 2 {
|
||||
if parts[1].0 != "ou" || parts[1].1 != ou || (parts[0].0 != "cn" && parts[0].0 != "uid")
|
||||
{
|
||||
Err(format!(
|
||||
r#"Unexpected DN format. Got "{}", expected: "uid=id,ou={},{}""#,
|
||||
dn, ou, base_dn_str
|
||||
))
|
||||
} else {
|
||||
Ok(parts[0].1.to_string())
|
||||
}
|
||||
} else {
|
||||
Err(format!(
|
||||
r#"Unexpected DN format. Got "{}", expected: "uid=id,ou={},{}""#,
|
||||
dn, ou, base_dn_str
|
||||
))
|
||||
}
|
||||
}
|
||||
.map_err(|s| LdapError {
|
||||
code: LdapResultCode::InvalidDNSyntax,
|
||||
message: s,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_user_id_from_distinguished_name(
|
||||
dn: &str,
|
||||
base_tree: &[(String, String)],
|
||||
base_dn_str: &str,
|
||||
) -> LdapResult<UserId> {
|
||||
get_id_from_distinguished_name(dn, base_tree, base_dn_str, false).map(UserId::from)
|
||||
}
|
||||
|
||||
pub fn get_group_id_from_distinguished_name(
|
||||
dn: &str,
|
||||
base_tree: &[(String, String)],
|
||||
base_dn_str: &str,
|
||||
) -> LdapResult<String> {
|
||||
get_id_from_distinguished_name(dn, base_tree, base_dn_str, true)
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
pub fn expand_attribute_wildcards<'a>(
|
||||
ldap_attributes: &'a [String],
|
||||
all_attribute_keys: &'a [&'static str],
|
||||
) -> Vec<&'a str> {
|
||||
let mut attributes_out = ldap_attributes
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if attributes_out.iter().any(|&x| x == "*") || attributes_out.is_empty() {
|
||||
// Remove occurrences of '*'
|
||||
attributes_out.retain(|&x| x != "*");
|
||||
// Splice in all non-operational attributes
|
||||
attributes_out.extend(all_attribute_keys.iter());
|
||||
}
|
||||
|
||||
// Deduplicate, preserving order
|
||||
let resolved_attributes = attributes_out
|
||||
.into_iter()
|
||||
.unique_by(|a| a.to_ascii_lowercase())
|
||||
.collect_vec();
|
||||
debug!(?ldap_attributes, ?resolved_attributes);
|
||||
resolved_attributes
|
||||
}
|
||||
|
||||
pub fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)]) -> bool {
|
||||
for (k, v) in subtree {
|
||||
assert!(k == &k.to_ascii_lowercase());
|
||||
assert!(v == &v.to_ascii_lowercase());
|
||||
}
|
||||
for (k, v) in base_tree {
|
||||
assert!(k == &k.to_ascii_lowercase());
|
||||
assert!(v == &v.to_ascii_lowercase());
|
||||
}
|
||||
if subtree.len() < base_tree.len() {
|
||||
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
|
||||
}
|
||||
|
||||
pub fn map_field(field: &str) -> Option<&'static str> {
|
||||
assert!(field == field.to_ascii_lowercase());
|
||||
Some(match field {
|
||||
"uid" => "user_id",
|
||||
"mail" => "email",
|
||||
"cn" | "displayname" => "display_name",
|
||||
"givenname" => "first_name",
|
||||
"sn" => "last_name",
|
||||
"avatar" => "avatar",
|
||||
"creationdate" | "createtimestamp" | "modifytimestamp" => "creation_date",
|
||||
"entryuuid" => "uuid",
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub struct LdapInfo {
|
||||
pub base_dn: Vec<(String, String)>,
|
||||
pub base_dn_str: String,
|
||||
pub ignored_user_attributes: Vec<String>,
|
||||
pub ignored_group_attributes: Vec<String>,
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod error;
|
||||
pub mod handler;
|
||||
pub mod ldap;
|
||||
pub mod opaque_handler;
|
||||
pub mod sql_backend_handler;
|
||||
pub mod sql_migrations;
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user