server: Add the attribute schema to the attributes in graphql

And make sure that we only request the schema once per top-level query
This commit is contained in:
Valentin Tolmer
2024-01-21 23:16:21 +01:00
committed by nitnelave
parent 1f2f034a48
commit e308a5e9a1
4 changed files with 229 additions and 181 deletions
+5 -4
View File
@@ -1,6 +1,7 @@
type AttributeValue { type AttributeValue {
name: String! name: String!
value: [String!]! value: [String!]!
schema: AttributeSchema!
} }
type Mutation { type Mutation {
@@ -152,10 +153,6 @@ type User {
groups: [Group!]! groups: [Group!]!
} }
type AttributeList {
attributes: [AttributeSchema!]!
}
enum AttributeType { enum AttributeType {
STRING STRING
INTEGER INTEGER
@@ -163,6 +160,10 @@ enum AttributeType {
DATE_TIME DATE_TIME
} }
type AttributeList {
attributes: [AttributeSchema!]!
}
type Success { type Success {
ok: Boolean! ok: Boolean!
} }
+1 -1
View File
@@ -4,7 +4,7 @@ use crate::domain::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
pub struct PublicSchema(Schema); pub struct PublicSchema(Schema);
impl PublicSchema { impl PublicSchema {
+6 -10
View File
@@ -1,3 +1,5 @@
use std::sync::Arc;
use crate::{ use crate::{
domain::{ domain::{
deserialize::deserialize_attribute_value, deserialize::deserialize_attribute_value,
@@ -159,11 +161,8 @@ impl<Handler: BackendHandler> Mutation<Handler> {
}) })
.instrument(span.clone()) .instrument(span.clone())
.await?; .await?;
Ok(handler let user_details = handler.get_user_details(&user_id).instrument(span).await?;
.get_user_details(&user_id) super::query::User::<Handler>::from_user(user_details, Arc::new(schema))
.instrument(span)
.await
.map(Into::into)?)
} }
async fn create_group( async fn create_group(
@@ -513,11 +512,8 @@ async fn create_group_with_details<Handler: BackendHandler>(
attributes, attributes,
}; };
let group_id = handler.create_group(request).await?; let group_id = handler.create_group(request).await?;
Ok(handler let group_details = handler.get_group_details(group_id).instrument(span).await?;
.get_group_details(group_id) super::query::Group::<Handler>::from_group_details(group_details, Arc::new(schema))
.instrument(span)
.await
.map(Into::into)?)
} }
fn deserialize_attribute( fn deserialize_attribute(
+217 -166
View File
@@ -1,13 +1,12 @@
use std::sync::Arc;
use crate::{ use crate::{
domain::{ domain::{
deserialize::deserialize_attribute_value, deserialize::deserialize_attribute_value,
handler::{BackendHandler, ReadSchemaBackendHandler}, handler::{BackendHandler, ReadSchemaBackendHandler},
ldap::utils::{map_user_field, UserFieldType}, ldap::utils::{map_user_field, UserFieldType},
model::UserColumn, model::UserColumn,
schema::{ schema::PublicSchema,
PublicSchema, SchemaAttributeExtractor, SchemaGroupAttributeExtractor,
SchemaUserAttributeExtractor,
},
types::{AttributeType, GroupDetails, GroupId, JpegPhoto, UserId}, types::{AttributeType, GroupDetails, GroupId, JpegPhoto, UserId},
}, },
infra::{ infra::{
@@ -143,11 +142,9 @@ impl<Handler: BackendHandler> Query<Handler> {
&span, &span,
"Unauthorized access to user data", "Unauthorized access to user data",
))?; ))?;
Ok(handler let schema = Arc::new(self.get_schema(context, span.clone()).await?);
.get_user_details(&user_id) let user = handler.get_user_details(&user_id).instrument(span).await?;
.instrument(span) User::<Handler>::from_user(user, schema)
.await
.map(Into::into)?)
} }
async fn users( async fn users(
@@ -164,8 +161,8 @@ impl<Handler: BackendHandler> Query<Handler> {
&span, &span,
"Unauthorized access to user list", "Unauthorized access to user list",
))?; ))?;
let schema = self.get_schema(context, span.clone()).await?; let schema = Arc::new(self.get_schema(context, span.clone()).await?);
Ok(handler let users = handler
.list_users( .list_users(
filters filters
.map(|f| f.try_into_domain_filter(&schema)) .map(|f| f.try_into_domain_filter(&schema))
@@ -173,8 +170,11 @@ impl<Handler: BackendHandler> Query<Handler> {
false, false,
) )
.instrument(span) .instrument(span)
.await .await?;
.map(|v| v.into_iter().map(Into::into).collect())?) users
.into_iter()
.map(|u| User::<Handler>::from_user_and_groups(u, schema.clone()))
.collect()
} }
async fn groups(context: &Context<Handler>) -> FieldResult<Vec<Group<Handler>>> { async fn groups(context: &Context<Handler>) -> FieldResult<Vec<Group<Handler>>> {
@@ -185,11 +185,12 @@ impl<Handler: BackendHandler> Query<Handler> {
&span, &span,
"Unauthorized access to group list", "Unauthorized access to group list",
))?; ))?;
Ok(handler let schema = Arc::new(self.get_schema(context, span.clone()).await?);
.list_groups(None) let domain_groups = handler.list_groups(None).instrument(span).await?;
.instrument(span) domain_groups
.await .into_iter()
.map(|v| v.into_iter().map(Into::into).collect())?) .map(|g| Group::<Handler>::from_group(g, schema.clone()))
.collect()
} }
async fn group(context: &Context<Handler>, group_id: i32) -> FieldResult<Group<Handler>> { async fn group(context: &Context<Handler>, group_id: i32) -> FieldResult<Group<Handler>> {
@@ -203,11 +204,12 @@ impl<Handler: BackendHandler> Query<Handler> {
&span, &span,
"Unauthorized access to group data", "Unauthorized access to group data",
))?; ))?;
Ok(handler let schema = Arc::new(self.get_schema(context, span.clone()).await?);
let group_details = handler
.get_group_details(GroupId(group_id)) .get_group_details(GroupId(group_id))
.instrument(span) .instrument(span)
.await .await?;
.map(Into::into)?) Group::<Handler>::from_group_details(group_details, schema.clone())
} }
async fn schema(context: &Context<Handler>) -> FieldResult<Schema<Handler>> { async fn schema(context: &Context<Handler>) -> FieldResult<Schema<Handler>> {
@@ -237,16 +239,45 @@ impl<Handler: BackendHandler> Query<Handler> {
/// Represents a single user. /// Represents a single user.
pub struct User<Handler: BackendHandler> { pub struct User<Handler: BackendHandler> {
user: DomainUser, user: DomainUser,
attributes: Vec<AttributeValue<Handler>>,
schema: Arc<PublicSchema>,
groups: Option<Vec<Group<Handler>>>,
_phantom: std::marker::PhantomData<Box<Handler>>, _phantom: std::marker::PhantomData<Box<Handler>>,
} }
#[cfg(test)] impl<Handler: BackendHandler> User<Handler> {
impl<Handler: BackendHandler> Default for User<Handler> { pub fn from_user(mut user: DomainUser, schema: Arc<PublicSchema>) -> FieldResult<Self> {
fn default() -> Self { let attributes = std::mem::take(&mut user.attributes);
Self { Ok(Self {
user: DomainUser::default(), user,
attributes: attributes
.into_iter()
.map(|a| {
AttributeValue::<Handler>::from_schema(a, &schema.get_schema().user_attributes)
})
.collect::<FieldResult<Vec<_>>>()?,
schema,
groups: None,
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
})
}
}
impl<Handler: BackendHandler> User<Handler> {
pub fn from_user_and_groups(
DomainUserAndGroups { user, groups }: DomainUserAndGroups,
schema: Arc<PublicSchema>,
) -> FieldResult<Self> {
let mut user = Self::from_user(user, schema.clone())?;
if let Some(groups) = groups {
user.groups = Some(
groups
.into_iter()
.map(|g| Group::<Handler>::from_group_details(g, schema.clone()))
.collect::<FieldResult<Vec<_>>>()?,
);
} }
Ok(user)
} }
} }
@@ -299,17 +330,15 @@ impl<Handler: BackendHandler> User<Handler> {
} }
/// User-defined attributes. /// User-defined attributes.
fn attributes(&self) -> Vec<AttributeValue<Handler, SchemaUserAttributeExtractor>> { fn attributes(&self) -> &[AttributeValue<Handler>] {
self.user &self.attributes
.attributes
.clone()
.into_iter()
.map(Into::into)
.collect()
} }
/// The groups to which this user belongs. /// The groups to which this user belongs.
async fn groups(&self, context: &Context<Handler>) -> FieldResult<Vec<Group<Handler>>> { async fn groups(&self, context: &Context<Handler>) -> FieldResult<Vec<Group<Handler>>> {
if let Some(groups) = &self.groups {
return Ok(groups.clone());
}
let span = debug_span!("[GraphQL query] user::groups"); let span = debug_span!("[GraphQL query] user::groups");
span.in_scope(|| { span.in_scope(|| {
debug!(user_id = ?self.user.user_id); debug!(user_id = ?self.user.user_id);
@@ -317,36 +346,16 @@ impl<Handler: BackendHandler> User<Handler> {
let handler = context let handler = context
.get_readable_handler(&self.user.user_id) .get_readable_handler(&self.user.user_id)
.expect("We shouldn't be able to get there without readable permission"); .expect("We shouldn't be able to get there without readable permission");
Ok(handler let domain_groups = handler
.get_user_groups(&self.user.user_id) .get_user_groups(&self.user.user_id)
.instrument(span) .instrument(span)
.await .await?;
.map(|set| { let mut groups = domain_groups
let mut groups = set .into_iter()
.into_iter() .map(|g| Group::<Handler>::from_group_details(g, self.schema.clone()))
.map(Into::into) .collect::<FieldResult<Vec<Group<Handler>>>>()?;
.collect::<Vec<Group<Handler>>>(); groups.sort_by(|g1, g2| g1.display_name.cmp(&g2.display_name));
groups.sort_by(|g1, g2| g1.display_name.cmp(&g2.display_name)); Ok(groups)
groups
})?)
}
}
impl<Handler: BackendHandler> From<DomainUser> for User<Handler> {
fn from(user: DomainUser) -> Self {
Self {
user,
_phantom: std::marker::PhantomData,
}
}
}
impl<Handler: BackendHandler> From<DomainUserAndGroups> for User<Handler> {
fn from(user: DomainUserAndGroups) -> Self {
Self {
user: user.user,
_phantom: std::marker::PhantomData,
}
} }
} }
@@ -357,11 +366,69 @@ pub struct Group<Handler: BackendHandler> {
display_name: String, display_name: String,
creation_date: chrono::NaiveDateTime, creation_date: chrono::NaiveDateTime,
uuid: String, uuid: String,
attributes: Vec<DomainAttributeValue>, attributes: Vec<AttributeValue<Handler>>,
members: Option<Vec<String>>, schema: Arc<PublicSchema>,
_phantom: std::marker::PhantomData<Box<Handler>>, _phantom: std::marker::PhantomData<Box<Handler>>,
} }
impl<Handler: BackendHandler> Group<Handler> {
pub fn from_group(
group: DomainGroup,
schema: Arc<PublicSchema>,
) -> FieldResult<Group<Handler>> {
Ok(Self {
group_id: group.id.0,
display_name: group.display_name.to_string(),
creation_date: group.creation_date,
uuid: group.uuid.into_string(),
attributes: group
.attributes
.into_iter()
.map(|a| {
AttributeValue::<Handler>::from_schema(a, &schema.get_schema().group_attributes)
})
.collect::<FieldResult<Vec<_>>>()?,
schema,
_phantom: std::marker::PhantomData,
})
}
pub fn from_group_details(
group_details: GroupDetails,
schema: Arc<PublicSchema>,
) -> FieldResult<Group<Handler>> {
Ok(Self {
group_id: group_details.group_id.0,
display_name: group_details.display_name.to_string(),
creation_date: group_details.creation_date,
uuid: group_details.uuid.into_string(),
attributes: group_details
.attributes
.into_iter()
.map(|a| {
AttributeValue::<Handler>::from_schema(a, &schema.get_schema().group_attributes)
})
.collect::<FieldResult<Vec<_>>>()?,
schema,
_phantom: std::marker::PhantomData,
})
}
}
impl<Handler: BackendHandler> Clone for Group<Handler> {
fn clone(&self) -> Self {
Self {
group_id: self.group_id,
display_name: self.display_name.clone(),
creation_date: self.creation_date,
uuid: self.uuid.clone(),
attributes: self.attributes.clone(),
schema: self.schema.clone(),
_phantom: std::marker::PhantomData,
}
}
}
#[graphql_object(context = Context<Handler>)] #[graphql_object(context = Context<Handler>)]
impl<Handler: BackendHandler> Group<Handler> { impl<Handler: BackendHandler> Group<Handler> {
fn id(&self) -> i32 { fn id(&self) -> i32 {
@@ -378,12 +445,8 @@ impl<Handler: BackendHandler> Group<Handler> {
} }
/// User-defined attributes. /// User-defined attributes.
fn attributes(&self) -> Vec<AttributeValue<Handler, SchemaGroupAttributeExtractor>> { fn attributes(&self) -> &[AttributeValue<Handler>] {
self.attributes &self.attributes
.clone()
.into_iter()
.map(Into::into)
.collect()
} }
/// The groups to which this user belongs. /// The groups to which this user belongs.
@@ -398,42 +461,17 @@ impl<Handler: BackendHandler> Group<Handler> {
&span, &span,
"Unauthorized access to group data", "Unauthorized access to group data",
))?; ))?;
Ok(handler let domain_users = handler
.list_users( .list_users(
Some(DomainRequestFilter::MemberOfId(GroupId(self.group_id))), Some(DomainRequestFilter::MemberOfId(GroupId(self.group_id))),
false, false,
) )
.instrument(span) .instrument(span)
.await .await?;
.map(|v| v.into_iter().map(Into::into).collect())?) domain_users
} .into_iter()
} .map(|u| User::<Handler>::from_user_and_groups(u, self.schema.clone()))
.collect()
impl<Handler: BackendHandler> From<GroupDetails> for Group<Handler> {
fn from(group_details: GroupDetails) -> Self {
Self {
group_id: group_details.group_id.0,
display_name: group_details.display_name.to_string(),
creation_date: group_details.creation_date,
uuid: group_details.uuid.into_string(),
attributes: group_details.attributes,
members: None,
_phantom: std::marker::PhantomData,
}
}
}
impl<Handler: BackendHandler> From<DomainGroup> for Group<Handler> {
fn from(group: DomainGroup) -> Self {
Self {
group_id: group.id.0,
display_name: group.display_name.to_string(),
creation_date: group.creation_date,
uuid: group.uuid.into_string(),
attributes: group.attributes,
members: Some(group.users.into_iter().map(UserId::into_string).collect()),
_phantom: std::marker::PhantomData,
}
} }
} }
@@ -465,6 +503,15 @@ impl<Handler: BackendHandler> AttributeSchema<Handler> {
} }
} }
impl<Handler: BackendHandler> Clone for AttributeSchema<Handler> {
fn clone(&self) -> Self {
Self {
schema: self.schema.clone(),
_phantom: std::marker::PhantomData,
}
}
}
impl<Handler: BackendHandler> From<DomainAttributeSchema> for AttributeSchema<Handler> { impl<Handler: BackendHandler> From<DomainAttributeSchema> for AttributeSchema<Handler> {
fn from(value: DomainAttributeSchema) -> Self { fn from(value: DomainAttributeSchema) -> Self {
Self { Self {
@@ -527,88 +574,92 @@ impl<Handler: BackendHandler> From<PublicSchema> for Schema<Handler> {
} }
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct AttributeValue<Handler: BackendHandler, Extractor> { pub struct AttributeValue<Handler: BackendHandler> {
attribute: DomainAttributeValue, attribute: DomainAttributeValue,
schema: AttributeSchema<Handler>,
_phantom: std::marker::PhantomData<Box<Handler>>, _phantom: std::marker::PhantomData<Box<Handler>>,
_phantom_extractor: std::marker::PhantomData<Extractor>,
} }
#[graphql_object(context = Context<Handler>)] #[graphql_object(context = Context<Handler>)]
impl<Handler: BackendHandler, Extractor: SchemaAttributeExtractor> impl<Handler: BackendHandler> AttributeValue<Handler> {
AttributeValue<Handler, Extractor>
{
fn name(&self) -> &str { fn name(&self) -> &str {
self.attribute.name.as_str() self.attribute.name.as_str()
} }
async fn value(&self, context: &Context<Handler>) -> FieldResult<Vec<String>> {
let handler = context fn value(&self) -> FieldResult<Vec<String>> {
.handler Ok(serialize_attribute(&self.attribute, &self.schema.schema))
.get_user_restricted_lister_handler(&context.validation_result); }
serialize_attribute(
&self.attribute, fn schema(&self) -> &AttributeSchema<Handler> {
Extractor::get_attributes(&PublicSchema::from(handler.get_schema().await?)), &self.schema
) }
}
impl<Handler: BackendHandler> Clone for AttributeValue<Handler> {
fn clone(&self) -> Self {
Self {
attribute: self.attribute.clone(),
schema: self.schema.clone(),
_phantom: std::marker::PhantomData,
}
} }
} }
pub fn serialize_attribute( pub fn serialize_attribute(
attribute: &DomainAttributeValue, attribute: &DomainAttributeValue,
attributes: &DomainAttributeList, attribute_schema: &DomainAttributeSchema,
) -> FieldResult<Vec<String>> { ) -> Vec<String> {
let convert_date = |date| chrono::Utc.from_utc_datetime(&date).to_rfc3339(); let convert_date = |date| chrono::Utc.from_utc_datetime(&date).to_rfc3339();
attributes match (attribute_schema.attribute_type, attribute_schema.is_list) {
.get_attribute_type(&attribute.name) (AttributeType::String, false) => vec![attribute.value.unwrap::<String>()],
.map(|attribute_type| { (AttributeType::Integer, false) => {
match attribute_type { // LDAP integers are encoded as strings.
(AttributeType::String, false) => { vec![attribute.value.unwrap::<i64>().to_string()]
vec![attribute.value.unwrap::<String>()] }
} (AttributeType::JpegPhoto, false) => {
(AttributeType::Integer, false) => { vec![String::from(&attribute.value.unwrap::<JpegPhoto>())]
// LDAP integers are encoded as strings. }
vec![attribute.value.unwrap::<i64>().to_string()] (AttributeType::DateTime, false) => {
} vec![convert_date(attribute.value.unwrap::<NaiveDateTime>())]
(AttributeType::JpegPhoto, false) => { }
vec![String::from(&attribute.value.unwrap::<JpegPhoto>())] (AttributeType::String, true) => attribute
} .value
(AttributeType::DateTime, false) => { .unwrap::<Vec<String>>()
vec![convert_date(attribute.value.unwrap::<NaiveDateTime>())] .into_iter()
} .collect(),
(AttributeType::String, true) => attribute (AttributeType::Integer, true) => attribute
.value .value
.unwrap::<Vec<String>>() .unwrap::<Vec<i64>>()
.into_iter() .into_iter()
.collect(), .map(|i| i.to_string())
(AttributeType::Integer, true) => attribute .collect(),
.value (AttributeType::JpegPhoto, true) => attribute
.unwrap::<Vec<i64>>() .value
.into_iter() .unwrap::<Vec<JpegPhoto>>()
.map(|i| i.to_string()) .iter()
.collect(), .map(String::from)
(AttributeType::JpegPhoto, true) => attribute .collect(),
.value (AttributeType::DateTime, true) => attribute
.unwrap::<Vec<JpegPhoto>>() .value
.iter() .unwrap::<Vec<NaiveDateTime>>()
.map(String::from) .into_iter()
.collect(), .map(convert_date)
(AttributeType::DateTime, true) => attribute .collect(),
.value }
.unwrap::<Vec<NaiveDateTime>>()
.into_iter()
.map(convert_date)
.collect(),
}
})
.ok_or_else(|| FieldError::from(anyhow::anyhow!("Unknown attribute: {}", &attribute.name)))
} }
impl<Handler: BackendHandler, Extractor> From<DomainAttributeValue> impl<Handler: BackendHandler> AttributeValue<Handler> {
for AttributeValue<Handler, Extractor> fn from_schema(a: DomainAttributeValue, schema: &DomainAttributeList) -> FieldResult<Self> {
{ match schema.get_attribute_schema(&a.name) {
fn from(value: DomainAttributeValue) -> Self { Some(s) => Ok(AttributeValue::<Handler> {
Self { attribute: a,
attribute: value, schema: AttributeSchema::<Handler> {
_phantom: std::marker::PhantomData, schema: s.clone(),
_phantom_extractor: std::marker::PhantomData, _phantom: std::marker::PhantomData,
},
_phantom: std::marker::PhantomData,
}),
None => Err(FieldError::from(format!("Unknown attribute {}", &a.name))),
} }
} }
} }