server: make attributes names, group names and emails case insensitive

In addition, group names and emails keep their casing
This commit is contained in:
Valentin Tolmer
2023-12-15 22:28:59 +01:00
committed by nitnelave
parent 71d37b9e5e
commit 272c84c574
27 changed files with 721 additions and 328 deletions

View File

@@ -7,7 +7,7 @@ use crate::domain::{
handler::{GroupListerBackendHandler, GroupRequestFilter},
ldap::error::LdapError,
schema::{PublicSchema, SchemaGroupAttributeExtractor},
types::{Group, UserId, Uuid},
types::{AttributeName, Group, UserId, Uuid},
};
use super::{
@@ -23,15 +23,15 @@ pub fn get_group_attribute(
base_dn_str: &str,
attribute: &str,
user_filter: &Option<UserId>,
ignored_group_attributes: &[String],
ignored_group_attributes: &[AttributeName],
schema: &PublicSchema,
) -> Option<Vec<Vec<u8>>> {
let attribute = attribute.to_ascii_lowercase();
let attribute = AttributeName::from(attribute);
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" | "id" => vec![group.display_name.clone().into_bytes()],
"cn" | "uid" | "id" => vec![group.display_name.to_string().into_bytes()],
"entryuuid" | "uuid" => vec![group.uuid.to_string().into_bytes()],
"member" | "uniquemember" => group
.users
@@ -48,11 +48,11 @@ pub fn get_group_attribute(
attribute
)
}
attr => {
_ => {
if !ignored_group_attributes.contains(&attribute) {
match get_custom_attribute::<SchemaGroupAttributeExtractor>(
&group.attributes,
attr,
&attribute,
schema,
) {
Some(v) => return Some(v),
@@ -91,7 +91,7 @@ fn make_ldap_search_group_result_entry(
base_dn_str: &str,
attributes: &[String],
user_filter: &Option<UserId>,
ignored_group_attributes: &[String],
ignored_group_attributes: &[AttributeName],
schema: &PublicSchema,
) -> LdapSearchResultEntry {
let expanded_attributes = expand_group_attribute_wildcards(attributes);
@@ -125,12 +125,12 @@ fn convert_group_filter(
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();
let field = AttributeName::from(field.as_str());
let value = value.to_ascii_lowercase();
match field.as_str() {
"member" | "uniquemember" => {
let user_name = get_user_id_from_distinguished_name(
value,
&value,
&ldap_info.base_dn,
&ldap_info.base_dn_str,
)?;
@@ -150,8 +150,8 @@ fn convert_group_filter(
warn!("Invalid dn filter on group: {}", value);
GroupRequestFilter::from(false)
})),
_ => match map_group_field(field) {
Some("display_name") => Ok(GroupRequestFilter::DisplayName(value.to_string())),
_ => match map_group_field(&field) {
Some("display_name") => Ok(GroupRequestFilter::DisplayName(value.into())),
Some("uuid") => Ok(GroupRequestFilter::Uuid(
Uuid::try_from(value.as_str()).map_err(|e| LdapError {
code: LdapResultCode::InappropriateMatching,
@@ -159,9 +159,9 @@ fn convert_group_filter(
})?,
)),
_ => {
if !ldap_info.ignored_group_attributes.contains(field) {
if !ldap_info.ignored_group_attributes.contains(&field) {
warn!(
r#"Ignoring unknown group attribute "{:?}" in filter.\n\
r#"Ignoring unknown group attribute "{}" in filter.\n\
To disable this warning, add it to "ignored_group_attributes" in the config."#,
field
);
@@ -179,24 +179,24 @@ fn convert_group_filter(
)),
LdapFilter::Not(filter) => Ok(GroupRequestFilter::Not(Box::new(rec(filter)?))),
LdapFilter::Present(field) => {
let field = &field.to_ascii_lowercase();
let field = AttributeName::from(field.as_str());
Ok(GroupRequestFilter::from(
field == "objectclass"
|| field == "dn"
|| field == "distinguishedname"
|| map_group_field(field).is_some(),
field.as_str() == "objectclass"
|| field.as_str() == "dn"
|| field.as_str() == "distinguishedname"
|| map_group_field(&field).is_some(),
))
}
LdapFilter::Substring(field, substring_filter) => {
let field = &field.to_ascii_lowercase();
match map_group_field(field.as_str()) {
let field = AttributeName::from(field.as_str());
match map_group_field(&field) {
Some("display_name") => Ok(GroupRequestFilter::DisplayNameSubString(
substring_filter.clone().into(),
)),
_ => Err(LdapError {
code: LdapResultCode::UnwillingToPerform,
message: format!(
"Unsupported group attribute for substring filter: {:?}",
"Unsupported group attribute for substring filter: \"{}\"",
field
),
}),

View File

@@ -14,7 +14,7 @@ use crate::domain::{
},
},
schema::{PublicSchema, SchemaUserAttributeExtractor},
types::{GroupDetails, User, UserAndGroups, UserColumn, UserId},
types::{AttributeName, GroupDetails, User, UserAndGroups, UserColumn, UserId},
};
pub fn get_user_attribute(
@@ -22,10 +22,10 @@ pub fn get_user_attribute(
attribute: &str,
base_dn_str: &str,
groups: Option<&[GroupDetails]>,
ignored_user_attributes: &[String],
ignored_user_attributes: &[AttributeName],
schema: &PublicSchema,
) -> Option<Vec<Vec<u8>>> {
let attribute = attribute.to_ascii_lowercase();
let attribute = AttributeName::from(attribute);
let attribute_values = match attribute.as_str() {
"objectclass" => vec![
b"inetOrgPerson".to_vec(),
@@ -37,20 +37,22 @@ pub fn get_user_attribute(
"dn" | "distinguishedname" => return None,
"uid" | "user_id" | "id" => vec![user.user_id.to_string().into_bytes()],
"entryuuid" | "uuid" => vec![user.uuid.to_string().into_bytes()],
"mail" | "email" => vec![user.email.clone().into_bytes()],
"givenname" | "first_name" | "firstname" => get_custom_attribute::<
SchemaUserAttributeExtractor,
>(
&user.attributes, "first_name", schema
)?,
"mail" | "email" => vec![user.email.to_string().into_bytes()],
"givenname" | "first_name" | "firstname" => {
get_custom_attribute::<SchemaUserAttributeExtractor>(
&user.attributes,
&"first_name".into(),
schema,
)?
}
"sn" | "last_name" | "lastname" => get_custom_attribute::<SchemaUserAttributeExtractor>(
&user.attributes,
"last_name",
&"last_name".into(),
schema,
)?,
"jpegphoto" | "avatar" => get_custom_attribute::<SchemaUserAttributeExtractor>(
&user.attributes,
"avatar",
&"avatar".into(),
schema,
)?,
"memberof" => groups
@@ -80,7 +82,7 @@ pub fn get_user_attribute(
if !ignored_user_attributes.contains(&attribute) {
match get_custom_attribute::<SchemaUserAttributeExtractor>(
&user.attributes,
attr,
&attribute,
schema,
) {
Some(v) => return Some(v),
@@ -118,7 +120,7 @@ fn make_ldap_search_user_result_entry(
base_dn_str: &str,
attributes: &[String],
groups: Option<&[GroupDetails]>,
ignored_user_attributes: &[String],
ignored_user_attributes: &[AttributeName],
schema: &PublicSchema,
) -> LdapSearchResultEntry {
let expanded_attributes = expand_user_attribute_wildcards(attributes);
@@ -156,7 +158,7 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
)),
LdapFilter::Not(filter) => Ok(UserRequestFilter::Not(Box::new(rec(filter)?))),
LdapFilter::Equality(field, value) => {
let field = &field.to_ascii_lowercase();
let field = AttributeName::from(field.as_str());
match field.as_str() {
"memberof" => Ok(UserRequestFilter::MemberOf(
get_group_id_from_distinguished_name(
@@ -179,7 +181,7 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
warn!("Invalid dn filter on user: {}", value);
UserRequestFilter::from(false)
})),
_ => match map_user_field(field) {
_ => match map_user_field(&field) {
UserFieldType::PrimaryField(UserColumn::UserId) => {
Ok(UserRequestFilter::UserId(UserId::new(value)))
}
@@ -187,11 +189,11 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
Ok(UserRequestFilter::Equality(field, value.clone()))
}
UserFieldType::Attribute(field) => Ok(UserRequestFilter::AttributeEquality(
field.to_owned(),
AttributeName::from(field),
value.clone(),
)),
UserFieldType::NoMatch => {
if !ldap_info.ignored_user_attributes.contains(field) {
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"#,
@@ -204,18 +206,18 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
}
}
LdapFilter::Present(field) => {
let field = &field.to_ascii_lowercase();
let field = AttributeName::from(field.as_str());
// Check that it's a field we support.
Ok(UserRequestFilter::from(
field == "objectclass"
|| field == "dn"
|| field == "distinguishedname"
|| !matches!(map_user_field(field), UserFieldType::NoMatch),
field.as_str() == "objectclass"
|| field.as_str() == "dn"
|| field.as_str() == "distinguishedname"
|| !matches!(map_user_field(&field), UserFieldType::NoMatch),
))
}
LdapFilter::Substring(field, substring_filter) => {
let field = &field.to_ascii_lowercase();
match map_user_field(field.as_str()) {
let field = AttributeName::from(field.as_str());
match map_user_field(&field) {
UserFieldType::PrimaryField(UserColumn::UserId) => Ok(
UserRequestFilter::UserIdSubString(substring_filter.clone().into()),
),

View File

@@ -7,7 +7,9 @@ use crate::domain::{
handler::SubStringFilter,
ldap::error::{LdapError, LdapResult},
schema::{PublicSchema, SchemaAttributeExtractor},
types::{AttributeType, AttributeValue, JpegPhoto, UserColumn, UserId},
types::{
AttributeName, AttributeType, AttributeValue, GroupName, JpegPhoto, UserColumn, UserId,
},
};
impl From<LdapSubstringFilter> for SubStringFilter {
@@ -103,8 +105,8 @@ 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)
) -> LdapResult<GroupName> {
get_id_from_distinguished_name(dn, base_tree, base_dn_str, true).map(GroupName::from)
}
#[instrument(skip(all_attribute_keys), level = "debug")]
@@ -160,9 +162,8 @@ pub enum UserFieldType {
Attribute(&'static str),
}
pub fn map_user_field(field: &str) -> UserFieldType {
assert!(field == field.to_ascii_lowercase());
match field {
pub fn map_user_field(field: &AttributeName) -> UserFieldType {
match field.as_str() {
"uid" | "user_id" | "id" => UserFieldType::PrimaryField(UserColumn::UserId),
"mail" | "email" => UserFieldType::PrimaryField(UserColumn::Email),
"cn" | "displayname" | "display_name" => {
@@ -179,9 +180,8 @@ pub fn map_user_field(field: &str) -> UserFieldType {
}
}
pub fn map_group_field(field: &str) -> Option<&'static str> {
assert!(field == field.to_ascii_lowercase());
Some(match field {
pub fn map_group_field(field: &AttributeName) -> Option<&'static str> {
Some(match field.as_str() {
"cn" | "displayname" | "uid" | "display_name" => "display_name",
"creationdate" | "createtimestamp" | "modifytimestamp" | "creation_date" => "creation_date",
"entryuuid" | "uuid" => "uuid",
@@ -192,13 +192,13 @@ pub fn map_group_field(field: &str) -> Option<&'static str> {
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>,
pub ignored_user_attributes: Vec<AttributeName>,
pub ignored_group_attributes: Vec<AttributeName>,
}
pub fn get_custom_attribute<Extractor: SchemaAttributeExtractor>(
attributes: &[AttributeValue],
attribute_name: &str,
attribute_name: &AttributeName,
schema: &PublicSchema,
) -> Option<Vec<Vec<u8>>> {
let convert_date = |date| {
@@ -212,7 +212,7 @@ pub fn get_custom_attribute<Extractor: SchemaAttributeExtractor>(
.and_then(|attribute_type| {
attributes
.iter()
.find(|a| a.name == attribute_name)
.find(|a| &a.name == attribute_name)
.map(|attribute| match attribute_type {
(AttributeType::String, false) => {
vec![attribute.value.unwrap::<String>().into_bytes()]