From 39a75b2c354cde4e659c19baae86d8e95cf6389b Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Fri, 15 Sep 2023 00:53:24 +0200 Subject: [PATCH] server: read custom attributes from LDAP --- server/src/domain/ldap/group.rs | 25 ++++-- server/src/domain/ldap/user.rs | 48 ++++++++---- server/src/domain/ldap/utils.rs | 10 +-- server/src/domain/mod.rs | 1 + server/src/domain/schema.rs | 124 ++++++++++++++++++++++++++++++ server/src/infra/graphql/query.rs | 32 ++------ server/src/infra/ldap_handler.rs | 105 ++++++++++++++++++++++++- server/src/infra/mod.rs | 1 - server/src/infra/schema.rs | 104 ------------------------- 9 files changed, 289 insertions(+), 161 deletions(-) create mode 100644 server/src/domain/schema.rs diff --git a/server/src/domain/ldap/group.rs b/server/src/domain/ldap/group.rs index d4df918..e6fcdc2 100644 --- a/server/src/domain/ldap/group.rs +++ b/server/src/domain/ldap/group.rs @@ -6,13 +6,14 @@ use tracing::{debug, instrument, warn}; use crate::domain::{ handler::{GroupListerBackendHandler, GroupRequestFilter}, ldap::error::LdapError, + schema::{PublicSchema, SchemaGroupAttributeExtractor}, types::{Group, UserId, Uuid}, }; use super::{ error::LdapResult, utils::{ - expand_attribute_wildcards, get_group_id_from_distinguished_name, + expand_attribute_wildcards, get_custom_attribute, get_group_id_from_distinguished_name, get_user_id_from_distinguished_name, map_group_field, LdapInfo, }, }; @@ -23,6 +24,7 @@ pub fn get_group_attribute( attribute: &str, user_filter: &Option, ignored_group_attributes: &[String], + schema: &PublicSchema, ) -> Option>> { let attribute = attribute.to_ascii_lowercase(); let attribute_values = match attribute.as_str() { @@ -46,13 +48,20 @@ pub fn get_group_attribute( attribute ) } - _ => { + attr => { if !ignored_group_attributes.contains(&attribute) { - warn!( - r#"Ignoring unrecognized group attribute: {}\n\ + match get_custom_attribute::( + &group.attributes, + attr, + schema, + ) { + Some(v) => return Some(v), + None => warn!( + r#"Ignoring unrecognized group attribute: {}\n\ To disable this warning, add it to "ignored_group_attributes" in the config."#, - attribute - ); + attribute + ), + }; } return None; } @@ -83,6 +92,7 @@ fn make_ldap_search_group_result_entry( attributes: &[String], user_filter: &Option, ignored_group_attributes: &[String], + schema: &PublicSchema, ) -> LdapSearchResultEntry { let expanded_attributes = expand_group_attribute_wildcards(attributes); @@ -97,6 +107,7 @@ fn make_ldap_search_group_result_entry( a, user_filter, ignored_group_attributes, + schema, )?; Some(LdapPartialAttribute { atype: a.to_string(), @@ -221,6 +232,7 @@ pub fn convert_groups_to_ldap_op<'a>( attributes: &'a [String], ldap_info: &'a LdapInfo, user_filter: &'a Option, + schema: &'a PublicSchema, ) -> impl Iterator + 'a { groups.into_iter().map(move |g| { LdapOp::SearchResultEntry(make_ldap_search_group_result_entry( @@ -229,6 +241,7 @@ pub fn convert_groups_to_ldap_op<'a>( attributes, user_filter, &ldap_info.ignored_group_attributes, + schema, )) }) } diff --git a/server/src/domain/ldap/user.rs b/server/src/domain/ldap/user.rs index 5841cde..3486b53 100644 --- a/server/src/domain/ldap/user.rs +++ b/server/src/domain/ldap/user.rs @@ -5,7 +5,7 @@ use ldap3_proto::{ use tracing::{debug, instrument, warn}; use crate::domain::{ - handler::{Schema, UserListerBackendHandler, UserRequestFilter}, + handler::{UserListerBackendHandler, UserRequestFilter}, ldap::{ error::{LdapError, LdapResult}, utils::{ @@ -13,6 +13,7 @@ use crate::domain::{ get_user_id_from_distinguished_name, map_user_field, LdapInfo, UserFieldType, }, }, + schema::{PublicSchema, SchemaUserAttributeExtractor}, types::{GroupDetails, User, UserAndGroups, UserColumn, UserId}, }; @@ -22,7 +23,7 @@ pub fn get_user_attribute( base_dn_str: &str, groups: Option<&[GroupDetails]>, ignored_user_attributes: &[String], - schema: &Schema, + schema: &PublicSchema, ) -> Option>> { let attribute = attribute.to_ascii_lowercase(); let attribute_values = match attribute.as_str() { @@ -37,13 +38,21 @@ pub fn get_user_attribute( "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(&user.attributes, "first_name", schema)? - } - "sn" | "last_name" | "lastname" => { - get_custom_attribute(&user.attributes, "last_name", schema)? - } - "jpegphoto" | "avatar" => get_custom_attribute(&user.attributes, "avatar", schema)?, + "givenname" | "first_name" | "firstname" => get_custom_attribute::< + SchemaUserAttributeExtractor, + >( + &user.attributes, "first_name", schema + )?, + "sn" | "last_name" | "lastname" => get_custom_attribute::( + &user.attributes, + "last_name", + schema, + )?, + "jpegphoto" | "avatar" => get_custom_attribute::( + &user.attributes, + "avatar", + schema, + )?, "memberof" => groups .into_iter() .flatten() @@ -67,13 +76,20 @@ pub fn get_user_attribute( attribute ) } - _ => { + attr => { if !ignored_user_attributes.contains(&attribute) { - warn!( - r#"Ignoring unrecognized group attribute: {}\n\ + match get_custom_attribute::( + &user.attributes, + attr, + schema, + ) { + Some(v) => return Some(v), + None => warn!( + r#"Ignoring unrecognized group attribute: {}\n\ To disable this warning, add it to "ignored_user_attributes" in the config."#, - attribute - ); + attr + ), + }; } return None; } @@ -103,7 +119,7 @@ fn make_ldap_search_user_result_entry( attributes: &[String], groups: Option<&[GroupDetails]>, ignored_user_attributes: &[String], - schema: &Schema, + schema: &PublicSchema, ) -> LdapSearchResultEntry { let expanded_attributes = expand_user_attribute_wildcards(attributes); let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str); @@ -253,7 +269,7 @@ pub fn convert_users_to_ldap_op<'a>( users: Vec, attributes: &'a [String], ldap_info: &'a LdapInfo, - schema: &'a Schema, + schema: &'a PublicSchema, ) -> impl Iterator + 'a { users.into_iter().map(move |u| { LdapOp::SearchResultEntry(make_ldap_search_user_result_entry( diff --git a/server/src/domain/ldap/utils.rs b/server/src/domain/ldap/utils.rs index 24b3db3..3de8ce6 100644 --- a/server/src/domain/ldap/utils.rs +++ b/server/src/domain/ldap/utils.rs @@ -4,8 +4,9 @@ use ldap3_proto::{proto::LdapSubstringFilter, LdapResultCode}; use tracing::{debug, instrument, warn}; use crate::domain::{ - handler::{Schema, SubStringFilter}, + handler::SubStringFilter, ldap::error::{LdapError, LdapResult}, + schema::{PublicSchema, SchemaAttributeExtractor}, types::{AttributeType, AttributeValue, JpegPhoto, UserColumn, UserId}, }; @@ -195,10 +196,10 @@ pub struct LdapInfo { pub ignored_group_attributes: Vec, } -pub fn get_custom_attribute( +pub fn get_custom_attribute( attributes: &[AttributeValue], attribute_name: &str, - schema: &Schema, + schema: &PublicSchema, ) -> Option>> { let convert_date = |date| { chrono::Utc @@ -206,8 +207,7 @@ pub fn get_custom_attribute( .to_rfc3339() .into_bytes() }; - schema - .user_attributes + Extractor::get_attributes(schema) .get_attribute_type(attribute_name) .and_then(|attribute_type| { attributes diff --git a/server/src/domain/mod.rs b/server/src/domain/mod.rs index 2fbd435..c1bb524 100644 --- a/server/src/domain/mod.rs +++ b/server/src/domain/mod.rs @@ -3,6 +3,7 @@ pub mod handler; pub mod ldap; pub mod model; pub mod opaque_handler; +pub mod schema; pub mod sql_backend_handler; pub mod sql_group_backend_handler; pub mod sql_migrations; diff --git a/server/src/domain/schema.rs b/server/src/domain/schema.rs new file mode 100644 index 0000000..0d920f5 --- /dev/null +++ b/server/src/domain/schema.rs @@ -0,0 +1,124 @@ +use crate::domain::{ + handler::{AttributeList, AttributeSchema, Schema}, + types::AttributeType, +}; +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] +pub struct PublicSchema(Schema); + +impl PublicSchema { + pub fn get_schema(&self) -> &Schema { + &self.0 + } +} + +pub trait SchemaAttributeExtractor: std::marker::Send { + fn get_attributes(schema: &PublicSchema) -> &AttributeList; +} + +pub struct SchemaUserAttributeExtractor; + +impl SchemaAttributeExtractor for SchemaUserAttributeExtractor { + fn get_attributes(schema: &PublicSchema) -> &AttributeList { + &schema.get_schema().user_attributes + } +} + +pub struct SchemaGroupAttributeExtractor; + +impl SchemaAttributeExtractor for SchemaGroupAttributeExtractor { + fn get_attributes(schema: &PublicSchema) -> &AttributeList { + &schema.get_schema().group_attributes + } +} + +impl From for PublicSchema { + fn from(mut schema: Schema) -> Self { + schema.user_attributes.attributes.extend_from_slice(&[ + AttributeSchema { + name: "user_id".to_owned(), + attribute_type: AttributeType::String, + is_list: false, + is_visible: true, + is_editable: false, + is_hardcoded: true, + }, + AttributeSchema { + name: "creation_date".to_owned(), + attribute_type: AttributeType::DateTime, + is_list: false, + is_visible: true, + is_editable: false, + is_hardcoded: true, + }, + AttributeSchema { + name: "mail".to_owned(), + attribute_type: AttributeType::String, + is_list: false, + is_visible: true, + is_editable: true, + is_hardcoded: true, + }, + AttributeSchema { + name: "uuid".to_owned(), + attribute_type: AttributeType::String, + is_list: false, + is_visible: true, + is_editable: false, + is_hardcoded: true, + }, + AttributeSchema { + name: "display_name".to_owned(), + attribute_type: AttributeType::String, + is_list: false, + is_visible: true, + is_editable: true, + is_hardcoded: true, + }, + ]); + schema + .user_attributes + .attributes + .sort_by(|a, b| a.name.cmp(&b.name)); + schema.group_attributes.attributes.extend_from_slice(&[ + AttributeSchema { + name: "group_id".to_owned(), + attribute_type: AttributeType::Integer, + is_list: false, + is_visible: true, + is_editable: false, + is_hardcoded: true, + }, + AttributeSchema { + name: "creation_date".to_owned(), + attribute_type: AttributeType::DateTime, + is_list: false, + is_visible: true, + is_editable: false, + is_hardcoded: true, + }, + AttributeSchema { + name: "uuid".to_owned(), + attribute_type: AttributeType::String, + is_list: false, + is_visible: true, + is_editable: false, + is_hardcoded: true, + }, + AttributeSchema { + name: "display_name".to_owned(), + attribute_type: AttributeType::String, + is_list: false, + is_visible: true, + is_editable: true, + is_hardcoded: true, + }, + ]); + schema + .group_attributes + .attributes + .sort_by(|a, b| a.name.cmp(&b.name)); + PublicSchema(schema) + } +} diff --git a/server/src/infra/graphql/query.rs b/server/src/infra/graphql/query.rs index 20764e1..633c8e8 100644 --- a/server/src/infra/graphql/query.rs +++ b/server/src/infra/graphql/query.rs @@ -2,12 +2,15 @@ use crate::{ domain::{ handler::{BackendHandler, SchemaBackendHandler}, ldap::utils::{map_user_field, UserFieldType}, + schema::{ + PublicSchema, SchemaAttributeExtractor, SchemaGroupAttributeExtractor, + SchemaUserAttributeExtractor, + }, types::{AttributeType, GroupDetails, GroupId, JpegPhoto, UserColumn, UserId}, }, infra::{ access_control::{ReadonlyBackendHandler, UserReadableBackendHandler}, graphql::api::{field_error_callback, Context}, - schema::PublicSchema, }, }; use chrono::{NaiveDateTime, TimeZone}; @@ -19,7 +22,6 @@ type DomainRequestFilter = crate::domain::handler::UserRequestFilter; type DomainUser = crate::domain::types::User; type DomainGroup = crate::domain::types::Group; type DomainUserAndGroups = crate::domain::types::UserAndGroups; -type DomainSchema = crate::infra::schema::PublicSchema; type DomainAttributeList = crate::domain::handler::AttributeList; type DomainAttributeSchema = crate::domain::handler::AttributeSchema; type DomainAttributeValue = crate::domain::types::AttributeValue; @@ -492,7 +494,7 @@ impl From for AttributeList { - schema: DomainSchema, + schema: PublicSchema, _phantom: std::marker::PhantomData>, } @@ -506,8 +508,8 @@ impl Schema { } } -impl From for Schema { - fn from(value: DomainSchema) -> Self { +impl From for Schema { + fn from(value: PublicSchema) -> Self { Self { schema: value, _phantom: std::marker::PhantomData, @@ -515,26 +517,6 @@ impl From for Schema { } } -trait SchemaAttributeExtractor: std::marker::Send { - fn get_attributes(schema: &DomainSchema) -> &DomainAttributeList; -} - -struct SchemaUserAttributeExtractor; - -impl SchemaAttributeExtractor for SchemaUserAttributeExtractor { - fn get_attributes(schema: &DomainSchema) -> &DomainAttributeList { - &schema.get_schema().user_attributes - } -} - -struct SchemaGroupAttributeExtractor; - -impl SchemaAttributeExtractor for SchemaGroupAttributeExtractor { - fn get_attributes(schema: &DomainSchema) -> &DomainAttributeList { - &schema.get_schema().group_attributes - } -} - #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct AttributeValue { attribute: DomainAttributeValue, diff --git a/server/src/infra/ldap_handler.rs b/server/src/infra/ldap_handler.rs index 6f6af12..cadc571 100644 --- a/server/src/infra/ldap_handler.rs +++ b/server/src/infra/ldap_handler.rs @@ -12,6 +12,7 @@ use crate::{ }, }, opaque_handler::OpaqueHandler, + schema::PublicSchema, types::{Group, JpegPhoto, UserAndGroups, UserId}, }, infra::access_control::{ @@ -611,10 +612,11 @@ impl LdapHandler { convert_users_to_ldap_op(users, &request.attrs, &self.ldap_info, &schema) @@ -623,6 +625,7 @@ impl LdapHandler &Schema { - &self.0 - } -} - -impl From for PublicSchema { - fn from(mut schema: Schema) -> Self { - schema.user_attributes.attributes.extend_from_slice(&[ - AttributeSchema { - name: "user_id".to_owned(), - attribute_type: AttributeType::String, - is_list: false, - is_visible: true, - is_editable: false, - is_hardcoded: true, - }, - AttributeSchema { - name: "creation_date".to_owned(), - attribute_type: AttributeType::DateTime, - is_list: false, - is_visible: true, - is_editable: false, - is_hardcoded: true, - }, - AttributeSchema { - name: "mail".to_owned(), - attribute_type: AttributeType::String, - is_list: false, - is_visible: true, - is_editable: true, - is_hardcoded: true, - }, - AttributeSchema { - name: "uuid".to_owned(), - attribute_type: AttributeType::String, - is_list: false, - is_visible: true, - is_editable: false, - is_hardcoded: true, - }, - AttributeSchema { - name: "display_name".to_owned(), - attribute_type: AttributeType::String, - is_list: false, - is_visible: true, - is_editable: true, - is_hardcoded: true, - }, - ]); - schema - .user_attributes - .attributes - .sort_by(|a, b| a.name.cmp(&b.name)); - schema.group_attributes.attributes.extend_from_slice(&[ - AttributeSchema { - name: "group_id".to_owned(), - attribute_type: AttributeType::Integer, - is_list: false, - is_visible: true, - is_editable: false, - is_hardcoded: true, - }, - AttributeSchema { - name: "creation_date".to_owned(), - attribute_type: AttributeType::DateTime, - is_list: false, - is_visible: true, - is_editable: false, - is_hardcoded: true, - }, - AttributeSchema { - name: "uuid".to_owned(), - attribute_type: AttributeType::String, - is_list: false, - is_visible: true, - is_editable: false, - is_hardcoded: true, - }, - AttributeSchema { - name: "display_name".to_owned(), - attribute_type: AttributeType::String, - is_list: false, - is_visible: true, - is_editable: true, - is_hardcoded: true, - }, - ]); - schema - .group_attributes - .attributes - .sort_by(|a, b| a.name.cmp(&b.name)); - PublicSchema(schema) - } -}