diff --git a/src/ldap/attr.rs b/src/ldap/attr.rs deleted file mode 100644 index 14bd72a..0000000 --- a/src/ldap/attr.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::fmt; -use std::str::FromStr; - -#[derive(Clone, Debug, PartialEq)] -pub struct UnknownLdapAttribute(String); - -impl fmt::Display for UnknownLdapAttribute { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Unknown LDAP attribute: {}", self.0) - } -} - -impl std::error::Error for UnknownLdapAttribute {} - -/// An LDAP attribute that is requested, or requested to be matched against an entry. -/// -/// Attributes are case-insensitive when parsing from a string. -/// -/// In the future, we may want to support custom attributes, but that is not -/// implemented for now. -#[derive(Clone, Debug, PartialEq)] -pub enum LdapAttribute { - Uid, - CommonName, - MemberOf, - ObjectClass, - Mail, - MailAlias, -} - -impl FromStr for LdapAttribute { - type Err = UnknownLdapAttribute; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "uid" => Ok(Self::Uid), - "cn" => Ok(Self::CommonName), - "memberof" => Ok(Self::MemberOf), - "objectclass" => Ok(Self::ObjectClass), - "mail" => Ok(Self::Mail), - "mailalias" => Ok(Self::MailAlias), - _ => Err(UnknownLdapAttribute(s.to_string())), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn uppercased_attribute() { - let s = "MemberOF"; - let attr = LdapAttribute::from_str(s); - println!("{attr:?}"); - assert_eq!(attr.unwrap(), LdapAttribute::MemberOf); - } - - #[test] - fn unknown_attribute() { - let s = "foobar"; - let attr = LdapAttribute::from_str(s); - println!("{attr:?}"); - assert_eq!(attr.unwrap_err(), UnknownLdapAttribute(s.to_string())); - } -} diff --git a/src/ldap/mod.rs b/src/ldap/mod.rs index 00f6174..59da6d2 100644 --- a/src/ldap/mod.rs +++ b/src/ldap/mod.rs @@ -1,5 +1,3 @@ -mod attr; -pub use attr::LdapAttribute; mod client_state; pub use client_state::LdapClientState; mod dn; diff --git a/src/ldap/op/search.rs b/src/ldap/op/search.rs index 0a29122..5d77fcf 100644 --- a/src/ldap/op/search.rs +++ b/src/ldap/op/search.rs @@ -4,11 +4,9 @@ use ldap3_proto::proto::{ }; use ldap3_proto::{LdapMsg, LdapResultCode}; -use std::str::FromStr; - use crate::db::error::BoxedError; use crate::db::{Database, DatabaseInterface, User}; -use crate::ldap::{Dn, LdapAttribute, LdapReturnError, LdapStream, LdapStreamError, MalformedDn}; +use crate::ldap::{Dn, LdapReturnError, LdapStream, LdapStreamError, MalformedDn}; #[derive(Debug)] pub struct InvalidSearchDn { @@ -148,42 +146,30 @@ pub async fn search_success( fn search_entry_from_user(user: &User, req_attrs: &[String]) -> LdapSearchResultEntry { let mut res: Vec = vec![]; - for attr_str in req_attrs { - // We keep a copy of the requested attribute so that we can answer as it was requested, - // eg. `CN` => `CN` (instead of normalizing to `cn`). - let Some(attr) = LdapAttribute::from_str(attr_str) - .inspect_err(|e| tracing::debug!("Unknown attribute in request: {e}")) - .ok() - else { - continue; - }; - - let str_values = match attr { - // TODO: should we normalize the value some more here? - LdapAttribute::Uid => vec![user.username.clone()], - // TODO: should CN be different than the mail? - // TODO: should we normalize values some more here? - LdapAttribute::CommonName | LdapAttribute::Mail | LdapAttribute::MailAlias => { - vec![user.mail.clone()] - } + for attr in req_attrs { + if let Some(attr_values) = match attr.as_str() { + "uid" => Some(vec![user.username.clone()]), + "cn" | "mail" => Some(vec![user.mail.clone()]), // TODO: group membership - LdapAttribute::MemberOf => vec![], - // TODO: if we introduce mail permission, we need to remove mailaccount from here - LdapAttribute::ObjectClass => vec![ - "inetOrgPerson".to_string(), - "posixAccount".to_string(), - "mailAccount".to_string(), - "person".to_string(), - ], - }; - - res.push(LdapPartialAttribute { - // Reuse the requested attribute name, not the normalized form - // we would otherwise produce. - atype: attr_str.clone(), - // LDAP response expects raw byte vec for each value - vals: str_values.into_iter().map(Vec::from).collect(), - }); + "memberof" => Some(vec![]), + // Copied from lldap output, not sure if we want to add/remove some classes depending on context + "objectclass" => Some( + vec!["inetOrgPerson", "posixAccount", "mailAccount", "person"] + .into_iter() + .map(String::from) + .collect(), + ), + _ => { + tracing::warn!("Ignoring unknown attr in search query: {attr}"); + None + } + } { + res.push(LdapPartialAttribute { + atype: attr.clone(), + // LDAP response expects raw byte vec for each value + vals: attr_values.into_iter().map(Vec::from).collect(), + }); + } } LdapSearchResultEntry { @@ -266,36 +252,24 @@ pub fn user_matches_filter(user: &User, filter: &LdapFilter) -> bool { false } LdapFilter::Not(sub_filter) => !user_matches_filter(user, sub_filter), - LdapFilter::Equality(attr, value) => LdapAttribute::from_str(attr).map_or_else( - |e| { - tracing::warn!("Unrecognized attribute, considering no match: {e}"); + LdapFilter::Equality(attr, value) => match attr.as_ref() { + "uid" => user.username == *value, + // TODO: should CN be different than the mail? + "cn" | "mail" | "mailAlias" => user.mail == *value, + // TODO: group membership + "memberof" => false, + "objectClass" => matches!( + value.as_ref(), + "inetOrgPerson" | "posixAccount" | "mailAccount" | "person" + ), + _ => { + tracing::warn!("Unknown user attribute filter, considering no match: {attr}"); false - }, - |attr| user_matches_attribute(user, &attr, value), - ), + } + }, _ => { tracing::warn!("Unimplemented search filter, considering no match: {filter:?}"); false } } } - -// TODO: we want to support group relations here as argument soon -pub fn user_matches_attribute(user: &User, attribute: &LdapAttribute, value: &str) -> bool { - match attribute { - // TODO: should we lowercase the value here? - LdapAttribute::Uid => user.username == *value, - // TODO: should CN be different than the mail? - // TODO: should we lowercase the value here? - LdapAttribute::CommonName | LdapAttribute::Mail | LdapAttribute::MailAlias => { - user.mail == *value - } - // TODO: group membership - LdapAttribute::MemberOf => false, - LdapAttribute::ObjectClass => matches!( - // We lowercase the value here because there's no ambiguity - value.to_lowercase().as_ref(), - "inetorgperson" | "posixaccount" | "mailaccount" | "person" - ), - } -}