server: read custom attributes from LDAP
This commit is contained in:
committed by
nitnelave
parent
8e1515c27b
commit
39a75b2c35
@@ -6,13 +6,14 @@ use tracing::{debug, instrument, warn};
|
|||||||
use crate::domain::{
|
use crate::domain::{
|
||||||
handler::{GroupListerBackendHandler, GroupRequestFilter},
|
handler::{GroupListerBackendHandler, GroupRequestFilter},
|
||||||
ldap::error::LdapError,
|
ldap::error::LdapError,
|
||||||
|
schema::{PublicSchema, SchemaGroupAttributeExtractor},
|
||||||
types::{Group, UserId, Uuid},
|
types::{Group, UserId, Uuid},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
error::LdapResult,
|
error::LdapResult,
|
||||||
utils::{
|
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,
|
get_user_id_from_distinguished_name, map_group_field, LdapInfo,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -23,6 +24,7 @@ pub fn get_group_attribute(
|
|||||||
attribute: &str,
|
attribute: &str,
|
||||||
user_filter: &Option<UserId>,
|
user_filter: &Option<UserId>,
|
||||||
ignored_group_attributes: &[String],
|
ignored_group_attributes: &[String],
|
||||||
|
schema: &PublicSchema,
|
||||||
) -> Option<Vec<Vec<u8>>> {
|
) -> Option<Vec<Vec<u8>>> {
|
||||||
let attribute = attribute.to_ascii_lowercase();
|
let attribute = attribute.to_ascii_lowercase();
|
||||||
let attribute_values = match attribute.as_str() {
|
let attribute_values = match attribute.as_str() {
|
||||||
@@ -46,13 +48,20 @@ pub fn get_group_attribute(
|
|||||||
attribute
|
attribute
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
attr => {
|
||||||
if !ignored_group_attributes.contains(&attribute) {
|
if !ignored_group_attributes.contains(&attribute) {
|
||||||
warn!(
|
match get_custom_attribute::<SchemaGroupAttributeExtractor>(
|
||||||
r#"Ignoring unrecognized group attribute: {}\n\
|
&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."#,
|
To disable this warning, add it to "ignored_group_attributes" in the config."#,
|
||||||
attribute
|
attribute
|
||||||
);
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -83,6 +92,7 @@ fn make_ldap_search_group_result_entry(
|
|||||||
attributes: &[String],
|
attributes: &[String],
|
||||||
user_filter: &Option<UserId>,
|
user_filter: &Option<UserId>,
|
||||||
ignored_group_attributes: &[String],
|
ignored_group_attributes: &[String],
|
||||||
|
schema: &PublicSchema,
|
||||||
) -> LdapSearchResultEntry {
|
) -> LdapSearchResultEntry {
|
||||||
let expanded_attributes = expand_group_attribute_wildcards(attributes);
|
let expanded_attributes = expand_group_attribute_wildcards(attributes);
|
||||||
|
|
||||||
@@ -97,6 +107,7 @@ fn make_ldap_search_group_result_entry(
|
|||||||
a,
|
a,
|
||||||
user_filter,
|
user_filter,
|
||||||
ignored_group_attributes,
|
ignored_group_attributes,
|
||||||
|
schema,
|
||||||
)?;
|
)?;
|
||||||
Some(LdapPartialAttribute {
|
Some(LdapPartialAttribute {
|
||||||
atype: a.to_string(),
|
atype: a.to_string(),
|
||||||
@@ -221,6 +232,7 @@ pub fn convert_groups_to_ldap_op<'a>(
|
|||||||
attributes: &'a [String],
|
attributes: &'a [String],
|
||||||
ldap_info: &'a LdapInfo,
|
ldap_info: &'a LdapInfo,
|
||||||
user_filter: &'a Option<UserId>,
|
user_filter: &'a Option<UserId>,
|
||||||
|
schema: &'a PublicSchema,
|
||||||
) -> impl Iterator<Item = LdapOp> + 'a {
|
) -> impl Iterator<Item = LdapOp> + 'a {
|
||||||
groups.into_iter().map(move |g| {
|
groups.into_iter().map(move |g| {
|
||||||
LdapOp::SearchResultEntry(make_ldap_search_group_result_entry(
|
LdapOp::SearchResultEntry(make_ldap_search_group_result_entry(
|
||||||
@@ -229,6 +241,7 @@ pub fn convert_groups_to_ldap_op<'a>(
|
|||||||
attributes,
|
attributes,
|
||||||
user_filter,
|
user_filter,
|
||||||
&ldap_info.ignored_group_attributes,
|
&ldap_info.ignored_group_attributes,
|
||||||
|
schema,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use ldap3_proto::{
|
|||||||
use tracing::{debug, instrument, warn};
|
use tracing::{debug, instrument, warn};
|
||||||
|
|
||||||
use crate::domain::{
|
use crate::domain::{
|
||||||
handler::{Schema, UserListerBackendHandler, UserRequestFilter},
|
handler::{UserListerBackendHandler, UserRequestFilter},
|
||||||
ldap::{
|
ldap::{
|
||||||
error::{LdapError, LdapResult},
|
error::{LdapError, LdapResult},
|
||||||
utils::{
|
utils::{
|
||||||
@@ -13,6 +13,7 @@ use crate::domain::{
|
|||||||
get_user_id_from_distinguished_name, map_user_field, LdapInfo, UserFieldType,
|
get_user_id_from_distinguished_name, map_user_field, LdapInfo, UserFieldType,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
schema::{PublicSchema, SchemaUserAttributeExtractor},
|
||||||
types::{GroupDetails, User, UserAndGroups, UserColumn, UserId},
|
types::{GroupDetails, User, UserAndGroups, UserColumn, UserId},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ pub fn get_user_attribute(
|
|||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
groups: Option<&[GroupDetails]>,
|
groups: Option<&[GroupDetails]>,
|
||||||
ignored_user_attributes: &[String],
|
ignored_user_attributes: &[String],
|
||||||
schema: &Schema,
|
schema: &PublicSchema,
|
||||||
) -> Option<Vec<Vec<u8>>> {
|
) -> Option<Vec<Vec<u8>>> {
|
||||||
let attribute = attribute.to_ascii_lowercase();
|
let attribute = attribute.to_ascii_lowercase();
|
||||||
let attribute_values = match attribute.as_str() {
|
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()],
|
"uid" | "user_id" | "id" => vec![user.user_id.to_string().into_bytes()],
|
||||||
"entryuuid" | "uuid" => vec![user.uuid.to_string().into_bytes()],
|
"entryuuid" | "uuid" => vec![user.uuid.to_string().into_bytes()],
|
||||||
"mail" | "email" => vec![user.email.clone().into_bytes()],
|
"mail" | "email" => vec![user.email.clone().into_bytes()],
|
||||||
"givenname" | "first_name" | "firstname" => {
|
"givenname" | "first_name" | "firstname" => get_custom_attribute::<
|
||||||
get_custom_attribute(&user.attributes, "first_name", schema)?
|
SchemaUserAttributeExtractor,
|
||||||
}
|
>(
|
||||||
"sn" | "last_name" | "lastname" => {
|
&user.attributes, "first_name", schema
|
||||||
get_custom_attribute(&user.attributes, "last_name", schema)?
|
)?,
|
||||||
}
|
"sn" | "last_name" | "lastname" => get_custom_attribute::<SchemaUserAttributeExtractor>(
|
||||||
"jpegphoto" | "avatar" => get_custom_attribute(&user.attributes, "avatar", schema)?,
|
&user.attributes,
|
||||||
|
"last_name",
|
||||||
|
schema,
|
||||||
|
)?,
|
||||||
|
"jpegphoto" | "avatar" => get_custom_attribute::<SchemaUserAttributeExtractor>(
|
||||||
|
&user.attributes,
|
||||||
|
"avatar",
|
||||||
|
schema,
|
||||||
|
)?,
|
||||||
"memberof" => groups
|
"memberof" => groups
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -67,13 +76,20 @@ pub fn get_user_attribute(
|
|||||||
attribute
|
attribute
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
attr => {
|
||||||
if !ignored_user_attributes.contains(&attribute) {
|
if !ignored_user_attributes.contains(&attribute) {
|
||||||
warn!(
|
match get_custom_attribute::<SchemaUserAttributeExtractor>(
|
||||||
r#"Ignoring unrecognized group attribute: {}\n\
|
&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."#,
|
To disable this warning, add it to "ignored_user_attributes" in the config."#,
|
||||||
attribute
|
attr
|
||||||
);
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -103,7 +119,7 @@ fn make_ldap_search_user_result_entry(
|
|||||||
attributes: &[String],
|
attributes: &[String],
|
||||||
groups: Option<&[GroupDetails]>,
|
groups: Option<&[GroupDetails]>,
|
||||||
ignored_user_attributes: &[String],
|
ignored_user_attributes: &[String],
|
||||||
schema: &Schema,
|
schema: &PublicSchema,
|
||||||
) -> LdapSearchResultEntry {
|
) -> LdapSearchResultEntry {
|
||||||
let expanded_attributes = expand_user_attribute_wildcards(attributes);
|
let expanded_attributes = expand_user_attribute_wildcards(attributes);
|
||||||
let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
|
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<UserAndGroups>,
|
users: Vec<UserAndGroups>,
|
||||||
attributes: &'a [String],
|
attributes: &'a [String],
|
||||||
ldap_info: &'a LdapInfo,
|
ldap_info: &'a LdapInfo,
|
||||||
schema: &'a Schema,
|
schema: &'a PublicSchema,
|
||||||
) -> impl Iterator<Item = LdapOp> + 'a {
|
) -> impl Iterator<Item = LdapOp> + 'a {
|
||||||
users.into_iter().map(move |u| {
|
users.into_iter().map(move |u| {
|
||||||
LdapOp::SearchResultEntry(make_ldap_search_user_result_entry(
|
LdapOp::SearchResultEntry(make_ldap_search_user_result_entry(
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ use ldap3_proto::{proto::LdapSubstringFilter, LdapResultCode};
|
|||||||
use tracing::{debug, instrument, warn};
|
use tracing::{debug, instrument, warn};
|
||||||
|
|
||||||
use crate::domain::{
|
use crate::domain::{
|
||||||
handler::{Schema, SubStringFilter},
|
handler::SubStringFilter,
|
||||||
ldap::error::{LdapError, LdapResult},
|
ldap::error::{LdapError, LdapResult},
|
||||||
|
schema::{PublicSchema, SchemaAttributeExtractor},
|
||||||
types::{AttributeType, AttributeValue, JpegPhoto, UserColumn, UserId},
|
types::{AttributeType, AttributeValue, JpegPhoto, UserColumn, UserId},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -195,10 +196,10 @@ pub struct LdapInfo {
|
|||||||
pub ignored_group_attributes: Vec<String>,
|
pub ignored_group_attributes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_custom_attribute(
|
pub fn get_custom_attribute<Extractor: SchemaAttributeExtractor>(
|
||||||
attributes: &[AttributeValue],
|
attributes: &[AttributeValue],
|
||||||
attribute_name: &str,
|
attribute_name: &str,
|
||||||
schema: &Schema,
|
schema: &PublicSchema,
|
||||||
) -> Option<Vec<Vec<u8>>> {
|
) -> Option<Vec<Vec<u8>>> {
|
||||||
let convert_date = |date| {
|
let convert_date = |date| {
|
||||||
chrono::Utc
|
chrono::Utc
|
||||||
@@ -206,8 +207,7 @@ pub fn get_custom_attribute(
|
|||||||
.to_rfc3339()
|
.to_rfc3339()
|
||||||
.into_bytes()
|
.into_bytes()
|
||||||
};
|
};
|
||||||
schema
|
Extractor::get_attributes(schema)
|
||||||
.user_attributes
|
|
||||||
.get_attribute_type(attribute_name)
|
.get_attribute_type(attribute_name)
|
||||||
.and_then(|attribute_type| {
|
.and_then(|attribute_type| {
|
||||||
attributes
|
attributes
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pub mod handler;
|
|||||||
pub mod ldap;
|
pub mod ldap;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod opaque_handler;
|
pub mod opaque_handler;
|
||||||
|
pub mod schema;
|
||||||
pub mod sql_backend_handler;
|
pub mod sql_backend_handler;
|
||||||
pub mod sql_group_backend_handler;
|
pub mod sql_group_backend_handler;
|
||||||
pub mod sql_migrations;
|
pub mod sql_migrations;
|
||||||
|
|||||||
124
server/src/domain/schema.rs
Normal file
124
server/src/domain/schema.rs
Normal file
@@ -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<Schema> 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,15 @@ use crate::{
|
|||||||
domain::{
|
domain::{
|
||||||
handler::{BackendHandler, SchemaBackendHandler},
|
handler::{BackendHandler, SchemaBackendHandler},
|
||||||
ldap::utils::{map_user_field, UserFieldType},
|
ldap::utils::{map_user_field, UserFieldType},
|
||||||
|
schema::{
|
||||||
|
PublicSchema, SchemaAttributeExtractor, SchemaGroupAttributeExtractor,
|
||||||
|
SchemaUserAttributeExtractor,
|
||||||
|
},
|
||||||
types::{AttributeType, GroupDetails, GroupId, JpegPhoto, UserColumn, UserId},
|
types::{AttributeType, GroupDetails, GroupId, JpegPhoto, UserColumn, UserId},
|
||||||
},
|
},
|
||||||
infra::{
|
infra::{
|
||||||
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler},
|
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler},
|
||||||
graphql::api::{field_error_callback, Context},
|
graphql::api::{field_error_callback, Context},
|
||||||
schema::PublicSchema,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use chrono::{NaiveDateTime, TimeZone};
|
use chrono::{NaiveDateTime, TimeZone};
|
||||||
@@ -19,7 +22,6 @@ type DomainRequestFilter = crate::domain::handler::UserRequestFilter;
|
|||||||
type DomainUser = crate::domain::types::User;
|
type DomainUser = crate::domain::types::User;
|
||||||
type DomainGroup = crate::domain::types::Group;
|
type DomainGroup = crate::domain::types::Group;
|
||||||
type DomainUserAndGroups = crate::domain::types::UserAndGroups;
|
type DomainUserAndGroups = crate::domain::types::UserAndGroups;
|
||||||
type DomainSchema = crate::infra::schema::PublicSchema;
|
|
||||||
type DomainAttributeList = crate::domain::handler::AttributeList;
|
type DomainAttributeList = crate::domain::handler::AttributeList;
|
||||||
type DomainAttributeSchema = crate::domain::handler::AttributeSchema;
|
type DomainAttributeSchema = crate::domain::handler::AttributeSchema;
|
||||||
type DomainAttributeValue = crate::domain::types::AttributeValue;
|
type DomainAttributeValue = crate::domain::types::AttributeValue;
|
||||||
@@ -492,7 +494,7 @@ impl<Handler: BackendHandler> From<DomainAttributeList> for AttributeList<Handle
|
|||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
pub struct Schema<Handler: BackendHandler> {
|
pub struct Schema<Handler: BackendHandler> {
|
||||||
schema: DomainSchema,
|
schema: PublicSchema,
|
||||||
_phantom: std::marker::PhantomData<Box<Handler>>,
|
_phantom: std::marker::PhantomData<Box<Handler>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,8 +508,8 @@ impl<Handler: BackendHandler> Schema<Handler> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Handler: BackendHandler> From<DomainSchema> for Schema<Handler> {
|
impl<Handler: BackendHandler> From<PublicSchema> for Schema<Handler> {
|
||||||
fn from(value: DomainSchema) -> Self {
|
fn from(value: PublicSchema) -> Self {
|
||||||
Self {
|
Self {
|
||||||
schema: value,
|
schema: value,
|
||||||
_phantom: std::marker::PhantomData,
|
_phantom: std::marker::PhantomData,
|
||||||
@@ -515,26 +517,6 @@ impl<Handler: BackendHandler> From<DomainSchema> for Schema<Handler> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
pub struct AttributeValue<Handler: BackendHandler, Extractor> {
|
pub struct AttributeValue<Handler: BackendHandler, Extractor> {
|
||||||
attribute: DomainAttributeValue,
|
attribute: DomainAttributeValue,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
opaque_handler::OpaqueHandler,
|
opaque_handler::OpaqueHandler,
|
||||||
|
schema::PublicSchema,
|
||||||
types::{Group, JpegPhoto, UserAndGroups, UserId},
|
types::{Group, JpegPhoto, UserAndGroups, UserId},
|
||||||
},
|
},
|
||||||
infra::access_control::{
|
infra::access_control::{
|
||||||
@@ -611,10 +612,11 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
.get_user_restricted_lister_handler(user_info);
|
.get_user_restricted_lister_handler(user_info);
|
||||||
let search_results = self.do_search_internal(&backend_handler, request).await?;
|
let search_results = self.do_search_internal(&backend_handler, request).await?;
|
||||||
|
|
||||||
let schema = backend_handler.get_schema().await.map_err(|e| LdapError {
|
let schema =
|
||||||
code: LdapResultCode::OperationsError,
|
PublicSchema::from(backend_handler.get_schema().await.map_err(|e| LdapError {
|
||||||
message: format!("Unable to get schema: {:#}", e),
|
code: LdapResultCode::OperationsError,
|
||||||
})?;
|
message: format!("Unable to get schema: {:#}", e),
|
||||||
|
})?);
|
||||||
let mut results = match search_results {
|
let mut results = match search_results {
|
||||||
InternalSearchResults::UsersAndGroups(users, groups) => {
|
InternalSearchResults::UsersAndGroups(users, groups) => {
|
||||||
convert_users_to_ldap_op(users, &request.attrs, &self.ldap_info, &schema)
|
convert_users_to_ldap_op(users, &request.attrs, &self.ldap_info, &schema)
|
||||||
@@ -623,6 +625,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
&request.attrs,
|
&request.attrs,
|
||||||
&self.ldap_info,
|
&self.ldap_info,
|
||||||
&backend_handler.user_filter,
|
&backend_handler.user_filter,
|
||||||
|
&schema,
|
||||||
))
|
))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -2723,4 +2726,98 @@ mod tests {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_custom_attribute_read() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
mock.expect_list_users().times(1).return_once(|_, _| {
|
||||||
|
Ok(vec![UserAndGroups {
|
||||||
|
user: User {
|
||||||
|
user_id: UserId::new("test"),
|
||||||
|
attributes: vec![AttributeValue {
|
||||||
|
name: "nickname".to_owned(),
|
||||||
|
value: Serialized::from("Bob the Builder"),
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
|
}])
|
||||||
|
});
|
||||||
|
mock.expect_list_groups().times(1).return_once(|_| {
|
||||||
|
Ok(vec![Group {
|
||||||
|
id: GroupId(1),
|
||||||
|
display_name: "group".to_string(),
|
||||||
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
|
users: vec![UserId::new("bob")],
|
||||||
|
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||||
|
attributes: vec![AttributeValue {
|
||||||
|
name: "club_name".to_owned(),
|
||||||
|
value: Serialized::from("Breakfast Club"),
|
||||||
|
}],
|
||||||
|
}])
|
||||||
|
});
|
||||||
|
mock.expect_get_schema().returning(|| {
|
||||||
|
Ok(crate::domain::handler::Schema {
|
||||||
|
user_attributes: AttributeList {
|
||||||
|
attributes: vec![AttributeSchema {
|
||||||
|
name: "nickname".to_owned(),
|
||||||
|
attribute_type: AttributeType::String,
|
||||||
|
is_list: false,
|
||||||
|
is_visible: true,
|
||||||
|
is_editable: true,
|
||||||
|
is_hardcoded: false,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
group_attributes: AttributeList {
|
||||||
|
attributes: vec![AttributeSchema {
|
||||||
|
name: "club_name".to_owned(),
|
||||||
|
attribute_type: AttributeType::String,
|
||||||
|
is_list: false,
|
||||||
|
is_visible: true,
|
||||||
|
is_editable: true,
|
||||||
|
is_hardcoded: false,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let mut ldap_handler = setup_bound_readonly_handler(mock).await;
|
||||||
|
|
||||||
|
let request = make_search_request(
|
||||||
|
"dc=example,dc=com",
|
||||||
|
LdapFilter::And(vec![]),
|
||||||
|
vec!["uid", "nickname", "club_name"],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.do_search_or_dse(&request).await,
|
||||||
|
Ok(vec![
|
||||||
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
||||||
|
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
||||||
|
attributes: vec![
|
||||||
|
LdapPartialAttribute {
|
||||||
|
atype: "uid".to_owned(),
|
||||||
|
vals: vec![b"test".to_vec()],
|
||||||
|
},
|
||||||
|
LdapPartialAttribute {
|
||||||
|
atype: "nickname".to_owned(),
|
||||||
|
vals: vec![b"Bob the Builder".to_vec()],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
||||||
|
dn: "cn=group,ou=groups,dc=example,dc=com".to_owned(),
|
||||||
|
attributes: vec![
|
||||||
|
LdapPartialAttribute {
|
||||||
|
atype: "uid".to_owned(),
|
||||||
|
vals: vec![b"group".to_vec()],
|
||||||
|
},
|
||||||
|
LdapPartialAttribute {
|
||||||
|
atype: "club_name".to_owned(),
|
||||||
|
vals: vec![b"Breakfast Club".to_vec()],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
make_search_success()
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ pub mod ldap_handler;
|
|||||||
pub mod ldap_server;
|
pub mod ldap_server;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod mail;
|
pub mod mail;
|
||||||
pub mod schema;
|
|
||||||
pub mod sql_backend_handler;
|
pub mod sql_backend_handler;
|
||||||
pub mod tcp_backend_handler;
|
pub mod tcp_backend_handler;
|
||||||
pub mod tcp_server;
|
pub mod tcp_server;
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
use crate::domain::{
|
|
||||||
handler::{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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Schema> 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user