mirror of
https://github.com/lldap/lldap.git
synced 2024-11-25 09:06:03 +00:00
server: Serialize attribute values when searching
This should fix #763 and allow filtering by custom attribute values.
This commit is contained in:
parent
337101edea
commit
c4be7f5b6f
10 changed files with 216 additions and 118 deletions
50
server/src/domain/deserialize.rs
Normal file
50
server/src/domain/deserialize.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use crate::domain::types::{AttributeType, JpegPhoto, Serialized};
|
||||
use anyhow::{bail, Context as AnyhowContext};
|
||||
|
||||
pub fn deserialize_attribute_value(
|
||||
value: &[String],
|
||||
typ: AttributeType,
|
||||
is_list: bool,
|
||||
) -> anyhow::Result<Serialized> {
|
||||
if !is_list && value.len() != 1 {
|
||||
bail!("Attribute is not a list, but multiple values were provided",);
|
||||
}
|
||||
let parse_int = |value: &String| -> anyhow::Result<i64> {
|
||||
value
|
||||
.parse::<i64>()
|
||||
.with_context(|| format!("Invalid integer value {}", value))
|
||||
};
|
||||
let parse_date = |value: &String| -> anyhow::Result<chrono::NaiveDateTime> {
|
||||
Ok(chrono::DateTime::parse_from_rfc3339(value)
|
||||
.with_context(|| format!("Invalid date value {}", value))?
|
||||
.naive_utc())
|
||||
};
|
||||
let parse_photo = |value: &String| -> anyhow::Result<JpegPhoto> {
|
||||
JpegPhoto::try_from(value.as_str()).context("Provided image is not a valid JPEG")
|
||||
};
|
||||
Ok(match (typ, is_list) {
|
||||
(AttributeType::String, false) => Serialized::from(&value[0]),
|
||||
(AttributeType::String, true) => Serialized::from(&value),
|
||||
(AttributeType::Integer, false) => Serialized::from(&parse_int(&value[0])?),
|
||||
(AttributeType::Integer, true) => Serialized::from(
|
||||
&value
|
||||
.iter()
|
||||
.map(parse_int)
|
||||
.collect::<anyhow::Result<Vec<_>>>()?,
|
||||
),
|
||||
(AttributeType::DateTime, false) => Serialized::from(&parse_date(&value[0])?),
|
||||
(AttributeType::DateTime, true) => Serialized::from(
|
||||
&value
|
||||
.iter()
|
||||
.map(parse_date)
|
||||
.collect::<anyhow::Result<Vec<_>>>()?,
|
||||
),
|
||||
(AttributeType::JpegPhoto, false) => Serialized::from(&parse_photo(&value[0])?),
|
||||
(AttributeType::JpegPhoto, true) => Serialized::from(
|
||||
&value
|
||||
.iter()
|
||||
.map(parse_photo)
|
||||
.collect::<anyhow::Result<Vec<_>>>()?,
|
||||
),
|
||||
})
|
||||
}
|
|
@ -2,7 +2,7 @@ use crate::domain::{
|
|||
error::Result,
|
||||
types::{
|
||||
AttributeName, AttributeType, AttributeValue, Email, Group, GroupDetails, GroupId,
|
||||
GroupName, JpegPhoto, User, UserAndGroups, UserColumn, UserId, Uuid,
|
||||
GroupName, JpegPhoto, Serialized, User, UserAndGroups, UserColumn, UserId, Uuid,
|
||||
},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
@ -54,7 +54,7 @@ pub enum UserRequestFilter {
|
|||
UserId(UserId),
|
||||
UserIdSubString(SubStringFilter),
|
||||
Equality(UserColumn, String),
|
||||
AttributeEquality(AttributeName, String),
|
||||
AttributeEquality(AttributeName, Serialized),
|
||||
SubString(UserColumn, SubStringFilter),
|
||||
// Check if a user belongs to a group identified by name.
|
||||
MemberOf(GroupName),
|
||||
|
|
|
@ -14,7 +14,7 @@ use super::{
|
|||
error::LdapResult,
|
||||
utils::{
|
||||
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, GroupFieldType, LdapInfo,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -124,8 +124,9 @@ fn make_ldap_search_group_result_entry(
|
|||
fn convert_group_filter(
|
||||
ldap_info: &LdapInfo,
|
||||
filter: &LdapFilter,
|
||||
schema: &PublicSchema,
|
||||
) -> LdapResult<GroupRequestFilter> {
|
||||
let rec = |f| convert_group_filter(ldap_info, f);
|
||||
let rec = |f| convert_group_filter(ldap_info, f, schema);
|
||||
match filter {
|
||||
LdapFilter::Equality(field, value) => {
|
||||
let field = AttributeName::from(field.as_str());
|
||||
|
@ -153,9 +154,11 @@ 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.into())),
|
||||
Some("uuid") => Ok(GroupRequestFilter::Uuid(
|
||||
_ => match map_group_field(&field, schema) {
|
||||
GroupFieldType::DisplayName => {
|
||||
Ok(GroupRequestFilter::DisplayName(value.into()))
|
||||
}
|
||||
GroupFieldType::Uuid => Ok(GroupRequestFilter::Uuid(
|
||||
Uuid::try_from(value.as_str()).map_err(|e| LdapError {
|
||||
code: LdapResultCode::InappropriateMatching,
|
||||
message: format!("Invalid UUID: {:#}", e),
|
||||
|
@ -187,13 +190,13 @@ fn convert_group_filter(
|
|||
field.as_str() == "objectclass"
|
||||
|| field.as_str() == "dn"
|
||||
|| field.as_str() == "distinguishedname"
|
||||
|| map_group_field(&field).is_some(),
|
||||
|| !matches!(map_group_field(&field, schema), GroupFieldType::NoMatch),
|
||||
))
|
||||
}
|
||||
LdapFilter::Substring(field, substring_filter) => {
|
||||
let field = AttributeName::from(field.as_str());
|
||||
match map_group_field(&field) {
|
||||
Some("display_name") => Ok(GroupRequestFilter::DisplayNameSubString(
|
||||
match map_group_field(&field, schema) {
|
||||
GroupFieldType::DisplayName => Ok(GroupRequestFilter::DisplayNameSubString(
|
||||
substring_filter.clone().into(),
|
||||
)),
|
||||
_ => Err(LdapError {
|
||||
|
@ -218,8 +221,9 @@ pub async fn get_groups_list<Backend: GroupListerBackendHandler>(
|
|||
ldap_filter: &LdapFilter,
|
||||
base: &str,
|
||||
backend: &Backend,
|
||||
schema: &PublicSchema,
|
||||
) -> LdapResult<Vec<Group>> {
|
||||
let filters = convert_group_filter(ldap_info, ldap_filter)?;
|
||||
let filters = convert_group_filter(ldap_info, ldap_filter, schema)?;
|
||||
debug!(?filters);
|
||||
backend
|
||||
.list_groups(Some(filters))
|
||||
|
|
|
@ -5,6 +5,7 @@ use ldap3_proto::{
|
|||
use tracing::{debug, instrument, warn};
|
||||
|
||||
use crate::domain::{
|
||||
deserialize::deserialize_attribute_value,
|
||||
handler::{UserListerBackendHandler, UserRequestFilter},
|
||||
ldap::{
|
||||
error::{LdapError, LdapResult},
|
||||
|
@ -14,7 +15,7 @@ use crate::domain::{
|
|||
},
|
||||
},
|
||||
schema::{PublicSchema, SchemaUserAttributeExtractor},
|
||||
types::{AttributeName, GroupDetails, User, UserAndGroups, UserColumn, UserId},
|
||||
types::{AttributeName, AttributeType, GroupDetails, User, UserAndGroups, UserColumn, UserId},
|
||||
};
|
||||
|
||||
pub fn get_user_attribute(
|
||||
|
@ -150,8 +151,26 @@ fn make_ldap_search_user_result_entry(
|
|||
}
|
||||
}
|
||||
|
||||
fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<UserRequestFilter> {
|
||||
let rec = |f| convert_user_filter(ldap_info, f);
|
||||
fn get_user_attribute_equality_filter(
|
||||
field: &AttributeName,
|
||||
typ: AttributeType,
|
||||
is_list: bool,
|
||||
value: &str,
|
||||
) -> LdapResult<UserRequestFilter> {
|
||||
deserialize_attribute_value(&[value.to_owned()], typ, is_list)
|
||||
.map_err(|e| LdapError {
|
||||
code: LdapResultCode::Other,
|
||||
message: format!("Invalid value for attribute {}: {}", field, e),
|
||||
})
|
||||
.map(|v| UserRequestFilter::AttributeEquality(field.clone(), v))
|
||||
}
|
||||
|
||||
fn convert_user_filter(
|
||||
ldap_info: &LdapInfo,
|
||||
filter: &LdapFilter,
|
||||
schema: &PublicSchema,
|
||||
) -> LdapResult<UserRequestFilter> {
|
||||
let rec = |f| convert_user_filter(ldap_info, f, schema);
|
||||
match filter {
|
||||
LdapFilter::And(filters) => Ok(UserRequestFilter::And(
|
||||
filters.iter().map(rec).collect::<LdapResult<_>>()?,
|
||||
|
@ -184,17 +203,16 @@ 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, schema) {
|
||||
UserFieldType::PrimaryField(UserColumn::UserId) => {
|
||||
Ok(UserRequestFilter::UserId(UserId::new(value)))
|
||||
}
|
||||
UserFieldType::PrimaryField(field) => {
|
||||
Ok(UserRequestFilter::Equality(field, value.clone()))
|
||||
}
|
||||
UserFieldType::Attribute(field) => Ok(UserRequestFilter::AttributeEquality(
|
||||
AttributeName::from(field),
|
||||
value.clone(),
|
||||
)),
|
||||
UserFieldType::Attribute(field, typ, is_list) => {
|
||||
get_user_attribute_equality_filter(&field, typ, is_list, value)
|
||||
}
|
||||
UserFieldType::NoMatch => {
|
||||
if !ldap_info.ignored_user_attributes.contains(&field) {
|
||||
warn!(
|
||||
|
@ -215,17 +233,17 @@ fn convert_user_filter(ldap_info: &LdapInfo, filter: &LdapFilter) -> LdapResult<
|
|||
field.as_str() == "objectclass"
|
||||
|| field.as_str() == "dn"
|
||||
|| field.as_str() == "distinguishedname"
|
||||
|| !matches!(map_user_field(&field), UserFieldType::NoMatch),
|
||||
|| !matches!(map_user_field(&field, schema), UserFieldType::NoMatch),
|
||||
))
|
||||
}
|
||||
LdapFilter::Substring(field, substring_filter) => {
|
||||
let field = AttributeName::from(field.as_str());
|
||||
match map_user_field(&field) {
|
||||
match map_user_field(&field, schema) {
|
||||
UserFieldType::PrimaryField(UserColumn::UserId) => Ok(
|
||||
UserRequestFilter::UserIdSubString(substring_filter.clone().into()),
|
||||
),
|
||||
UserFieldType::NoMatch
|
||||
| UserFieldType::Attribute(_)
|
||||
| UserFieldType::Attribute(_, _, _)
|
||||
| UserFieldType::PrimaryField(UserColumn::CreationDate)
|
||||
| UserFieldType::PrimaryField(UserColumn::Uuid) => Err(LdapError {
|
||||
code: LdapResultCode::UnwillingToPerform,
|
||||
|
@ -258,8 +276,9 @@ pub async fn get_user_list<Backend: UserListerBackendHandler>(
|
|||
request_groups: bool,
|
||||
base: &str,
|
||||
backend: &Backend,
|
||||
schema: &PublicSchema,
|
||||
) -> LdapResult<Vec<UserAndGroups>> {
|
||||
let filters = convert_user_filter(ldap_info, ldap_filter)?;
|
||||
let filters = convert_user_filter(ldap_info, ldap_filter, schema)?;
|
||||
debug!(?filters);
|
||||
backend
|
||||
.list_users(Some(filters), request_groups)
|
||||
|
|
|
@ -159,34 +159,66 @@ pub fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)])
|
|||
pub enum UserFieldType {
|
||||
NoMatch,
|
||||
PrimaryField(UserColumn),
|
||||
Attribute(&'static str),
|
||||
Attribute(AttributeName, AttributeType, bool),
|
||||
}
|
||||
|
||||
pub fn map_user_field(field: &AttributeName) -> UserFieldType {
|
||||
pub fn map_user_field(field: &AttributeName, schema: &PublicSchema) -> UserFieldType {
|
||||
match field.as_str() {
|
||||
"uid" | "user_id" | "id" => UserFieldType::PrimaryField(UserColumn::UserId),
|
||||
"mail" | "email" => UserFieldType::PrimaryField(UserColumn::Email),
|
||||
"cn" | "displayname" | "display_name" => {
|
||||
UserFieldType::PrimaryField(UserColumn::DisplayName)
|
||||
}
|
||||
"givenname" | "first_name" | "firstname" => UserFieldType::Attribute("first_name"),
|
||||
"sn" | "last_name" | "lastname" => UserFieldType::Attribute("last_name"),
|
||||
"avatar" | "jpegphoto" => UserFieldType::Attribute("avatar"),
|
||||
"givenname" | "first_name" | "firstname" => UserFieldType::Attribute(
|
||||
AttributeName::from("first_name"),
|
||||
AttributeType::String,
|
||||
false,
|
||||
),
|
||||
"sn" | "last_name" | "lastname" => UserFieldType::Attribute(
|
||||
AttributeName::from("last_name"),
|
||||
AttributeType::String,
|
||||
false,
|
||||
),
|
||||
"avatar" | "jpegphoto" => UserFieldType::Attribute(
|
||||
AttributeName::from("avatar"),
|
||||
AttributeType::JpegPhoto,
|
||||
false,
|
||||
),
|
||||
"creationdate" | "createtimestamp" | "modifytimestamp" | "creation_date" => {
|
||||
UserFieldType::PrimaryField(UserColumn::CreationDate)
|
||||
}
|
||||
"entryuuid" | "uuid" => UserFieldType::PrimaryField(UserColumn::Uuid),
|
||||
_ => UserFieldType::NoMatch,
|
||||
_ => schema
|
||||
.get_schema()
|
||||
.user_attributes
|
||||
.get_attribute_type(field)
|
||||
.map(|(t, is_list)| UserFieldType::Attribute(field.clone(), t, is_list))
|
||||
.unwrap_or(UserFieldType::NoMatch),
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
_ => return None,
|
||||
})
|
||||
pub enum GroupFieldType {
|
||||
NoMatch,
|
||||
DisplayName,
|
||||
CreationDate,
|
||||
Uuid,
|
||||
Attribute(AttributeName, AttributeType, bool),
|
||||
}
|
||||
|
||||
pub fn map_group_field(field: &AttributeName, schema: &PublicSchema) -> GroupFieldType {
|
||||
match field.as_str() {
|
||||
"cn" | "displayname" | "uid" | "display_name" => GroupFieldType::DisplayName,
|
||||
"creationdate" | "createtimestamp" | "modifytimestamp" | "creation_date" => {
|
||||
GroupFieldType::CreationDate
|
||||
}
|
||||
"entryuuid" | "uuid" => GroupFieldType::Uuid,
|
||||
_ => schema
|
||||
.get_schema()
|
||||
.group_attributes
|
||||
.get_attribute_type(field)
|
||||
.map(|(t, is_list)| GroupFieldType::Attribute(field.clone(), t, is_list))
|
||||
.unwrap_or(GroupFieldType::NoMatch),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LdapInfo {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod deserialize;
|
||||
pub mod error;
|
||||
pub mod handler;
|
||||
pub mod ldap;
|
||||
|
|
|
@ -22,14 +22,14 @@ use sea_orm::{
|
|||
use std::collections::HashSet;
|
||||
use tracing::instrument;
|
||||
|
||||
fn attribute_condition(name: AttributeName, value: String) -> Cond {
|
||||
fn attribute_condition(name: AttributeName, value: Serialized) -> Cond {
|
||||
Expr::in_subquery(
|
||||
Expr::col(UserColumn::UserId.as_column_ref()),
|
||||
model::UserAttributes::find()
|
||||
.select_only()
|
||||
.column(model::UserAttributesColumn::UserId)
|
||||
.filter(model::UserAttributesColumn::AttributeName.eq(name))
|
||||
.filter(model::UserAttributesColumn::Value.eq(Serialized::from(&value)))
|
||||
.filter(model::UserAttributesColumn::Value.eq(value))
|
||||
.into_query(),
|
||||
)
|
||||
.into_condition()
|
||||
|
@ -463,7 +463,7 @@ mod tests {
|
|||
&fixture.handler,
|
||||
Some(UserRequestFilter::AttributeEquality(
|
||||
AttributeName::from("first_name"),
|
||||
"first bob".to_string(),
|
||||
Serialized::from("first bob"),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use crate::{
|
||||
domain::{
|
||||
deserialize::deserialize_attribute_value,
|
||||
handler::{
|
||||
AttributeList, BackendHandler, CreateAttributeRequest, CreateGroupRequest,
|
||||
CreateUserRequest, UpdateGroupRequest, UpdateUserRequest,
|
||||
},
|
||||
types::{
|
||||
AttributeName, AttributeType, AttributeValue as DomainAttributeValue, GroupId,
|
||||
JpegPhoto, Serialized, UserId,
|
||||
JpegPhoto, UserId,
|
||||
},
|
||||
},
|
||||
infra::{
|
||||
|
@ -535,54 +536,12 @@ fn deserialize_attribute(
|
|||
)
|
||||
.into());
|
||||
}
|
||||
if !attribute_schema.is_list && attribute.value.len() != 1 {
|
||||
return Err(anyhow!(
|
||||
"Attribute {} is not a list, but multiple values were provided",
|
||||
attribute.name
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let parse_int = |value: &String| -> FieldResult<i64> {
|
||||
Ok(value
|
||||
.parse::<i64>()
|
||||
.with_context(|| format!("Invalid integer value {}", value))?)
|
||||
};
|
||||
let parse_date = |value: &String| -> FieldResult<chrono::NaiveDateTime> {
|
||||
Ok(chrono::DateTime::parse_from_rfc3339(value)
|
||||
.with_context(|| format!("Invalid date value {}", value))?
|
||||
.naive_utc())
|
||||
};
|
||||
let parse_photo = |value: &String| -> FieldResult<JpegPhoto> {
|
||||
Ok(JpegPhoto::try_from(value.as_str()).context("Provided image is not a valid JPEG")?)
|
||||
};
|
||||
let deserialized_values = match (attribute_schema.attribute_type, attribute_schema.is_list) {
|
||||
(AttributeType::String, false) => Serialized::from(&attribute.value[0]),
|
||||
(AttributeType::String, true) => Serialized::from(&attribute.value),
|
||||
(AttributeType::Integer, false) => Serialized::from(&parse_int(&attribute.value[0])?),
|
||||
(AttributeType::Integer, true) => Serialized::from(
|
||||
&attribute
|
||||
.value
|
||||
.iter()
|
||||
.map(parse_int)
|
||||
.collect::<FieldResult<Vec<_>>>()?,
|
||||
),
|
||||
(AttributeType::DateTime, false) => Serialized::from(&parse_date(&attribute.value[0])?),
|
||||
(AttributeType::DateTime, true) => Serialized::from(
|
||||
&attribute
|
||||
.value
|
||||
.iter()
|
||||
.map(parse_date)
|
||||
.collect::<FieldResult<Vec<_>>>()?,
|
||||
),
|
||||
(AttributeType::JpegPhoto, false) => Serialized::from(&parse_photo(&attribute.value[0])?),
|
||||
(AttributeType::JpegPhoto, true) => Serialized::from(
|
||||
&attribute
|
||||
.value
|
||||
.iter()
|
||||
.map(parse_photo)
|
||||
.collect::<FieldResult<Vec<_>>>()?,
|
||||
),
|
||||
};
|
||||
let deserialized_values = deserialize_attribute_value(
|
||||
&attribute.value,
|
||||
attribute_schema.attribute_type,
|
||||
attribute_schema.is_list,
|
||||
)
|
||||
.context(format!("While deserializing attribute {}", attribute.name))?;
|
||||
Ok(DomainAttributeValue {
|
||||
name: attribute_name,
|
||||
value: deserialized_values,
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
use crate::{
|
||||
domain::{
|
||||
deserialize::deserialize_attribute_value,
|
||||
handler::{BackendHandler, ReadSchemaBackendHandler},
|
||||
ldap::utils::{map_user_field, UserFieldType},
|
||||
model::UserColumn,
|
||||
schema::{
|
||||
PublicSchema, SchemaAttributeExtractor, SchemaGroupAttributeExtractor,
|
||||
SchemaUserAttributeExtractor,
|
||||
},
|
||||
types::{
|
||||
AttributeName, AttributeType, GroupDetails, GroupId, JpegPhoto, UserColumn, UserId,
|
||||
},
|
||||
types::{AttributeType, GroupDetails, GroupId, JpegPhoto, UserId},
|
||||
},
|
||||
infra::{
|
||||
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler},
|
||||
graphql::api::{field_error_callback, Context},
|
||||
},
|
||||
};
|
||||
use anyhow::Context as AnyhowContext;
|
||||
use chrono::{NaiveDateTime, TimeZone};
|
||||
use juniper::{graphql_object, FieldError, FieldResult, GraphQLInputObject};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, debug_span, Instrument};
|
||||
use tracing::{debug, debug_span, Instrument, Span};
|
||||
|
||||
type DomainRequestFilter = crate::domain::handler::UserRequestFilter;
|
||||
type DomainUser = crate::domain::types::User;
|
||||
|
@ -40,9 +41,8 @@ pub struct RequestFilter {
|
|||
member_of_id: Option<i32>,
|
||||
}
|
||||
|
||||
impl TryInto<DomainRequestFilter> for RequestFilter {
|
||||
type Error = String;
|
||||
fn try_into(self) -> Result<DomainRequestFilter, Self::Error> {
|
||||
impl RequestFilter {
|
||||
fn try_into_domain_filter(self, schema: &PublicSchema) -> FieldResult<DomainRequestFilter> {
|
||||
match (
|
||||
self.eq,
|
||||
self.any,
|
||||
|
@ -52,33 +52,39 @@ impl TryInto<DomainRequestFilter> for RequestFilter {
|
|||
self.member_of_id,
|
||||
) {
|
||||
(Some(eq), None, None, None, None, None) => {
|
||||
match map_user_field(&eq.field.as_str().into()) {
|
||||
UserFieldType::NoMatch => Err(format!("Unknown request filter: {}", &eq.field)),
|
||||
match map_user_field(&eq.field.as_str().into(), schema) {
|
||||
UserFieldType::NoMatch => {
|
||||
Err(format!("Unknown request filter: {}", &eq.field).into())
|
||||
}
|
||||
UserFieldType::PrimaryField(UserColumn::UserId) => {
|
||||
Ok(DomainRequestFilter::UserId(UserId::new(&eq.value)))
|
||||
}
|
||||
UserFieldType::PrimaryField(column) => {
|
||||
Ok(DomainRequestFilter::Equality(column, eq.value))
|
||||
}
|
||||
UserFieldType::Attribute(column) => Ok(DomainRequestFilter::AttributeEquality(
|
||||
AttributeName::from(column),
|
||||
eq.value,
|
||||
)),
|
||||
UserFieldType::Attribute(name, typ, false) => {
|
||||
let value = deserialize_attribute_value(&[eq.value], typ, false)
|
||||
.context(format!("While deserializing attribute {}", &name))?;
|
||||
Ok(DomainRequestFilter::AttributeEquality(name, value))
|
||||
}
|
||||
UserFieldType::Attribute(_, _, true) => {
|
||||
Err("Equality not supported for list fields".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, Some(any), None, None, None, None) => Ok(DomainRequestFilter::Or(
|
||||
any.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<Vec<_>, String>>()?,
|
||||
.map(|f| f.try_into_domain_filter(schema))
|
||||
.collect::<FieldResult<Vec<_>>>()?,
|
||||
)),
|
||||
(None, None, Some(all), None, None, None) => Ok(DomainRequestFilter::And(
|
||||
all.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<Vec<_>, String>>()?,
|
||||
.map(|f| f.try_into_domain_filter(schema))
|
||||
.collect::<FieldResult<Vec<_>>>()?,
|
||||
)),
|
||||
(None, None, None, Some(not), None, None) => {
|
||||
Ok(DomainRequestFilter::Not(Box::new((*not).try_into()?)))
|
||||
}
|
||||
(None, None, None, Some(not), None, None) => Ok(DomainRequestFilter::Not(Box::new(
|
||||
(*not).try_into_domain_filter(schema)?,
|
||||
))),
|
||||
(None, None, None, None, Some(group), None) => {
|
||||
Ok(DomainRequestFilter::MemberOf(group.into()))
|
||||
}
|
||||
|
@ -86,9 +92,9 @@ impl TryInto<DomainRequestFilter> for RequestFilter {
|
|||
Ok(DomainRequestFilter::MemberOfId(GroupId(group_id)))
|
||||
}
|
||||
(None, None, None, None, None, None) => {
|
||||
Err("No field specified in request filter".to_string())
|
||||
Err("No field specified in request filter".into())
|
||||
}
|
||||
_ => Err("Multiple fields specified in request filter".to_string()),
|
||||
_ => Err("Multiple fields specified in request filter".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,8 +160,14 @@ impl<Handler: BackendHandler> Query<Handler> {
|
|||
&span,
|
||||
"Unauthorized access to user list",
|
||||
))?;
|
||||
let schema = self.get_schema(context, span.clone()).await?;
|
||||
Ok(handler
|
||||
.list_users(filters.map(TryInto::try_into).transpose()?, false)
|
||||
.list_users(
|
||||
filters
|
||||
.map(|f| f.try_into_domain_filter(&schema))
|
||||
.transpose()?,
|
||||
false,
|
||||
)
|
||||
.instrument(span)
|
||||
.await
|
||||
.map(|v| v.into_iter().map(Into::into).collect())?)
|
||||
|
@ -196,6 +208,16 @@ impl<Handler: BackendHandler> Query<Handler> {
|
|||
|
||||
async fn schema(context: &Context<Handler>) -> FieldResult<Schema<Handler>> {
|
||||
let span = debug_span!("[GraphQL query] get_schema");
|
||||
self.get_schema(context, span).await.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Handler: BackendHandler> Query<Handler> {
|
||||
async fn get_schema(
|
||||
&self,
|
||||
context: &Context<Handler>,
|
||||
span: Span,
|
||||
) -> FieldResult<PublicSchema> {
|
||||
let handler = context
|
||||
.handler
|
||||
.get_user_restricted_lister_handler(&context.validation_result);
|
||||
|
@ -203,8 +225,7 @@ impl<Handler: BackendHandler> Query<Handler> {
|
|||
.get_schema()
|
||||
.instrument(span)
|
||||
.await
|
||||
.map(Into::<PublicSchema>::into)
|
||||
.map(Into::into)?)
|
||||
.map(Into::<PublicSchema>::into)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -594,7 +615,7 @@ mod tests {
|
|||
use crate::{
|
||||
domain::{
|
||||
handler::AttributeList,
|
||||
types::{AttributeType, Serialized},
|
||||
types::{AttributeName, AttributeType, Serialized},
|
||||
},
|
||||
infra::{
|
||||
access_control::{Permission, ValidationResults},
|
||||
|
@ -795,6 +816,7 @@ mod tests {
|
|||
}"#;
|
||||
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
setup_default_schema(&mut mock);
|
||||
mock.expect_list_users()
|
||||
.with(
|
||||
eq(Some(DomainRequestFilter::Or(vec![
|
||||
|
@ -805,7 +827,7 @@ mod tests {
|
|||
),
|
||||
DomainRequestFilter::AttributeEquality(
|
||||
AttributeName::from("first_name"),
|
||||
"robert".to_owned(),
|
||||
Serialized::from("robert"),
|
||||
),
|
||||
]))),
|
||||
eq(false),
|
||||
|
|
|
@ -531,6 +531,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||
&self,
|
||||
backend_handler: &impl UserAndGroupListerBackendHandler,
|
||||
request: &LdapSearchRequest,
|
||||
schema: &PublicSchema,
|
||||
) -> LdapResult<InternalSearchResults> {
|
||||
let dn_parts = parse_distinguished_name(&request.base.to_ascii_lowercase())?;
|
||||
let scope = get_search_scope(&self.ldap_info.base_dn, &dn_parts, &request.scope);
|
||||
|
@ -554,11 +555,19 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||
need_groups,
|
||||
&request.base,
|
||||
backend_handler,
|
||||
schema,
|
||||
)
|
||||
.await
|
||||
});
|
||||
let get_group_list = cast(|filter: &LdapFilter| async {
|
||||
get_groups_list(&self.ldap_info, filter, &request.base, backend_handler).await
|
||||
get_groups_list(
|
||||
&self.ldap_info,
|
||||
filter,
|
||||
&request.base,
|
||||
backend_handler,
|
||||
schema,
|
||||
)
|
||||
.await
|
||||
});
|
||||
Ok(match scope {
|
||||
SearchScope::Global => InternalSearchResults::UsersAndGroups(
|
||||
|
@ -617,13 +626,15 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||
let backend_handler = self
|
||||
.backend_handler
|
||||
.get_user_restricted_lister_handler(user_info);
|
||||
let search_results = self.do_search_internal(&backend_handler, request).await?;
|
||||
|
||||
let schema =
|
||||
PublicSchema::from(backend_handler.get_schema().await.map_err(|e| LdapError {
|
||||
code: LdapResultCode::OperationsError,
|
||||
message: format!("Unable to get schema: {:#}", e),
|
||||
})?);
|
||||
let search_results = self
|
||||
.do_search_internal(&backend_handler, request, &schema)
|
||||
.await?;
|
||||
let mut results = match search_results {
|
||||
InternalSearchResults::UsersAndGroups(users, groups) => {
|
||||
convert_users_to_ldap_op(users, &request.attrs, &self.ldap_info, &schema)
|
||||
|
@ -1686,7 +1697,7 @@ mod tests {
|
|||
false.into(),
|
||||
UserRequestFilter::AttributeEquality(
|
||||
AttributeName::from("first_name"),
|
||||
"firstname".to_owned(),
|
||||
Serialized::from("firstname"),
|
||||
),
|
||||
false.into(),
|
||||
UserRequestFilter::UserIdSubString(SubStringFilter {
|
||||
|
|
Loading…
Reference in a new issue