mirror of
https://github.com/lldap/lldap.git
synced 2026-03-31 15:07:48 +01:00
domain + server: introduce new AttributeValue enum
This commit is contained in:
committed by
nitnelave
parent
cf0e9a01f1
commit
5d2f168554
+118
-2
@@ -130,6 +130,21 @@ impl Serialized {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttributeValue> for Serialized {
|
||||
fn from(val: AttributeValue) -> Serialized {
|
||||
match &val {
|
||||
AttributeValue::String(Cardinality::Singleton(s)) => Serialized::from(s),
|
||||
AttributeValue::String(Cardinality::Unbounded(l)) => Serialized::from(l),
|
||||
AttributeValue::Integer(Cardinality::Singleton(i)) => Serialized::from(i),
|
||||
AttributeValue::Integer(Cardinality::Unbounded(l)) => Serialized::from(l),
|
||||
AttributeValue::JpegPhoto(Cardinality::Singleton(p)) => Serialized::from(p),
|
||||
AttributeValue::JpegPhoto(Cardinality::Unbounded(l)) => Serialized::from(l),
|
||||
AttributeValue::DateTime(Cardinality::Singleton(dt)) => Serialized::from(dt),
|
||||
AttributeValue::DateTime(Cardinality::Unbounded(l)) => Serialized::from(l),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_str_case_insensitive(s1: &str, s2: &str) -> Ordering {
|
||||
let mut it_1 = s1.chars().flat_map(|c| c.to_lowercase());
|
||||
let mut it_2 = s2.chars().flat_map(|c| c.to_lowercase());
|
||||
@@ -292,7 +307,7 @@ impl AsRef<GroupName> for GroupName {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, DeriveValueType)]
|
||||
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, DeriveValueType, Hash)]
|
||||
#[sea_orm(column_type = "Binary(BlobSize::Long)", array_type = "Bytes")]
|
||||
pub struct JpegPhoto(#[serde(with = "serde_bytes")] Vec<u8>);
|
||||
|
||||
@@ -407,10 +422,111 @@ impl IntoActiveValue<Serialized> for JpegPhoto {
|
||||
}
|
||||
}
|
||||
|
||||
// Represents values that can be either a singleton or a list of a specific type
|
||||
// Used by AttributeValue to model attributes with types that might be a list.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Hash)]
|
||||
pub enum Cardinality<T: Clone> {
|
||||
Singleton(T),
|
||||
Unbounded(Vec<T>),
|
||||
}
|
||||
|
||||
impl<T: Clone> Cardinality<T> {
|
||||
pub fn into_vec(self) -> Vec<T> {
|
||||
match self {
|
||||
Self::Singleton(v) => vec![v],
|
||||
Self::Unbounded(l) => l,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Hash)]
|
||||
pub enum AttributeValue {
|
||||
String(Cardinality<String>),
|
||||
Integer(Cardinality<i64>),
|
||||
JpegPhoto(Cardinality<JpegPhoto>),
|
||||
DateTime(Cardinality<NaiveDateTime>),
|
||||
}
|
||||
|
||||
impl AttributeValue {
|
||||
pub fn get_attribute_type(&self) -> AttributeType {
|
||||
match self {
|
||||
Self::String(_) => AttributeType::String,
|
||||
Self::Integer(_) => AttributeType::Integer,
|
||||
Self::JpegPhoto(_) => AttributeType::JpegPhoto,
|
||||
Self::DateTime(_) => AttributeType::DateTime,
|
||||
}
|
||||
}
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
if let AttributeValue::String(Cardinality::Singleton(s)) = self {
|
||||
Some(s.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn into_string(self) -> Option<String> {
|
||||
if let AttributeValue::String(Cardinality::Singleton(s)) = self {
|
||||
Some(s)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn as_jpeg_photo(&self) -> Option<&JpegPhoto> {
|
||||
if let AttributeValue::JpegPhoto(Cardinality::Singleton(p)) = self {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for AttributeValue {
|
||||
fn from(s: String) -> Self {
|
||||
AttributeValue::String(Cardinality::Singleton(s))
|
||||
}
|
||||
}
|
||||
impl From<Vec<String>> for AttributeValue {
|
||||
fn from(l: Vec<String>) -> Self {
|
||||
AttributeValue::String(Cardinality::Unbounded(l))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for AttributeValue {
|
||||
fn from(i: i64) -> Self {
|
||||
AttributeValue::Integer(Cardinality::Singleton(i))
|
||||
}
|
||||
}
|
||||
impl From<Vec<i64>> for AttributeValue {
|
||||
fn from(l: Vec<i64>) -> Self {
|
||||
AttributeValue::Integer(Cardinality::Unbounded(l))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JpegPhoto> for AttributeValue {
|
||||
fn from(j: JpegPhoto) -> Self {
|
||||
AttributeValue::JpegPhoto(Cardinality::Singleton(j))
|
||||
}
|
||||
}
|
||||
impl From<Vec<JpegPhoto>> for AttributeValue {
|
||||
fn from(l: Vec<JpegPhoto>) -> Self {
|
||||
AttributeValue::JpegPhoto(Cardinality::Unbounded(l))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NaiveDateTime> for AttributeValue {
|
||||
fn from(dt: NaiveDateTime) -> Self {
|
||||
AttributeValue::DateTime(Cardinality::Singleton(dt))
|
||||
}
|
||||
}
|
||||
impl From<Vec<NaiveDateTime>> for AttributeValue {
|
||||
fn from(l: Vec<NaiveDateTime>) -> Self {
|
||||
AttributeValue::DateTime(Cardinality::Unbounded(l))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Hash)]
|
||||
pub struct Attribute {
|
||||
pub name: AttributeName,
|
||||
pub value: Serialized,
|
||||
pub value: AttributeValue,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,50 +1,41 @@
|
||||
use anyhow::{bail, Context as AnyhowContext};
|
||||
use lldap_domain::types::{AttributeType, JpegPhoto, Serialized};
|
||||
use anyhow::{bail, Context as AnyhowContext, Result};
|
||||
use lldap_domain::types::{AttributeType, AttributeValue, JpegPhoto};
|
||||
|
||||
pub fn deserialize_attribute_value(
|
||||
value: &[String],
|
||||
typ: AttributeType,
|
||||
is_list: bool,
|
||||
) -> anyhow::Result<Serialized> {
|
||||
) -> Result<AttributeValue> {
|
||||
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> {
|
||||
let parse_int = |value: &String| -> Result<i64> {
|
||||
value
|
||||
.parse::<i64>()
|
||||
.with_context(|| format!("Invalid integer value {}", value))
|
||||
};
|
||||
let parse_date = |value: &String| -> anyhow::Result<chrono::NaiveDateTime> {
|
||||
let parse_date = |value: &String| -> 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> {
|
||||
let parse_photo = |value: &String| -> 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<_>>>()?,
|
||||
),
|
||||
(AttributeType::String, false) => value[0].clone().into(),
|
||||
(AttributeType::String, true) => value.to_vec().into(),
|
||||
(AttributeType::Integer, false) => (parse_int(&value[0])?).into(),
|
||||
(AttributeType::Integer, true) => {
|
||||
(value.iter().map(parse_int).collect::<Result<Vec<_>>>()?).into()
|
||||
}
|
||||
(AttributeType::DateTime, false) => (parse_date(&value[0])?).into(),
|
||||
(AttributeType::DateTime, true) => {
|
||||
(value.iter().map(parse_date).collect::<Result<Vec<_>>>()?).into()
|
||||
}
|
||||
(AttributeType::JpegPhoto, false) => (parse_photo(&value[0])?).into(),
|
||||
(AttributeType::JpegPhoto, true) => {
|
||||
(value.iter().map(parse_photo).collect::<Result<Vec<_>>>()?).into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ use lldap_domain::{
|
||||
},
|
||||
schema::Schema,
|
||||
types::{
|
||||
AttributeName, Group, GroupDetails, GroupId, GroupName, LdapObjectClass, Serialized, User,
|
||||
UserAndGroups, UserId, Uuid,
|
||||
AttributeName, AttributeValue, Group, GroupDetails, GroupId, GroupName, LdapObjectClass,
|
||||
User, UserAndGroups, UserId, Uuid,
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -59,7 +59,7 @@ pub enum UserRequestFilter {
|
||||
UserId(UserId),
|
||||
UserIdSubString(SubStringFilter),
|
||||
Equality(UserColumn, String),
|
||||
AttributeEquality(AttributeName, Serialized),
|
||||
AttributeEquality(AttributeName, AttributeValue),
|
||||
SubString(UserColumn, SubStringFilter),
|
||||
// Check if a user belongs to a group identified by name.
|
||||
MemberOf(GroupName),
|
||||
@@ -89,7 +89,7 @@ pub enum GroupRequestFilter {
|
||||
GroupId(GroupId),
|
||||
// Check if the group contains a user identified by uid.
|
||||
Member(UserId),
|
||||
AttributeEquality(AttributeName, Serialized),
|
||||
AttributeEquality(AttributeName, AttributeValue),
|
||||
CustomAttributePresent(AttributeName),
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::domain::{
|
||||
GroupFieldType, LdapInfo,
|
||||
},
|
||||
},
|
||||
schema::{PublicSchema, SchemaGroupAttributeExtractor},
|
||||
schema::PublicSchema,
|
||||
};
|
||||
use lldap_domain::types::{
|
||||
AttributeName, AttributeType, Group, GroupId, LdapObjectClass, UserId, Uuid,
|
||||
@@ -62,9 +62,7 @@ pub fn get_group_attribute(
|
||||
.map(|u| format!("uid={},ou=people,{}", u, base_dn_str).into_bytes())
|
||||
.collect(),
|
||||
GroupFieldType::Uuid => vec![group.uuid.to_string().into_bytes()],
|
||||
GroupFieldType::Attribute(attr, _, _) => {
|
||||
get_custom_attribute::<SchemaGroupAttributeExtractor>(&group.attributes, &attr, schema)?
|
||||
}
|
||||
GroupFieldType::Attribute(attr, _, _) => get_custom_attribute(&group.attributes, &attr)?,
|
||||
GroupFieldType::NoMatch => match attribute.as_str() {
|
||||
"1.1" => return None,
|
||||
// We ignore the operational attribute wildcard
|
||||
@@ -79,10 +77,9 @@ pub fn get_group_attribute(
|
||||
if ignored_group_attributes.contains(attribute) {
|
||||
return None;
|
||||
}
|
||||
get_custom_attribute::<SchemaGroupAttributeExtractor>(
|
||||
get_custom_attribute(
|
||||
&group.attributes,
|
||||
attribute,
|
||||
schema,
|
||||
).or_else(||{warn!(
|
||||
r#"Ignoring unrecognized group attribute: {}. To disable this warning, add it to "ignored_group_attributes" in the config."#,
|
||||
attribute
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::domain::{
|
||||
},
|
||||
},
|
||||
model::UserColumn,
|
||||
schema::{PublicSchema, SchemaUserAttributeExtractor},
|
||||
schema::PublicSchema,
|
||||
};
|
||||
use lldap_domain::types::{
|
||||
AttributeName, AttributeType, GroupDetails, LdapObjectClass, User, UserAndGroups, UserId,
|
||||
@@ -78,9 +78,7 @@ pub fn get_user_attribute(
|
||||
.from_utc_datetime(&user.creation_date)
|
||||
.to_rfc3339()
|
||||
.into_bytes()],
|
||||
UserFieldType::Attribute(attr, _, _) => {
|
||||
get_custom_attribute::<SchemaUserAttributeExtractor>(&user.attributes, &attr, schema)?
|
||||
}
|
||||
UserFieldType::Attribute(attr, _, _) => get_custom_attribute(&user.attributes, &attr)?,
|
||||
UserFieldType::NoMatch => match attribute.as_str() {
|
||||
"1.1" => return None,
|
||||
// We ignore the operational attribute wildcard.
|
||||
@@ -95,12 +93,7 @@ pub fn get_user_attribute(
|
||||
if ignored_user_attributes.contains(attribute) {
|
||||
return None;
|
||||
}
|
||||
get_custom_attribute::<SchemaUserAttributeExtractor>(
|
||||
&user.attributes,
|
||||
attribute,
|
||||
schema,
|
||||
)
|
||||
.or_else(|| {
|
||||
get_custom_attribute(&user.attributes, attribute).or_else(|| {
|
||||
warn!(
|
||||
r#"Ignoring unrecognized user attribute: {}. To disable this warning, add it to "ignored_user_attributes" in the config."#,
|
||||
attribute
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use chrono::{NaiveDateTime, TimeZone};
|
||||
use chrono::TimeZone;
|
||||
use ldap3_proto::{proto::LdapSubstringFilter, LdapResultCode};
|
||||
use tracing::{debug, instrument, warn};
|
||||
|
||||
@@ -8,9 +8,11 @@ use crate::domain::{
|
||||
handler::SubStringFilter,
|
||||
ldap::error::{LdapError, LdapResult},
|
||||
model::UserColumn,
|
||||
schema::{PublicSchema, SchemaAttributeExtractor},
|
||||
schema::PublicSchema,
|
||||
};
|
||||
use lldap_domain::types::{
|
||||
Attribute, AttributeName, AttributeType, AttributeValue, Cardinality, GroupName, UserId,
|
||||
};
|
||||
use lldap_domain::types::{Attribute, AttributeName, AttributeType, GroupName, JpegPhoto, UserId};
|
||||
|
||||
impl From<LdapSubstringFilter> for SubStringFilter {
|
||||
fn from(
|
||||
@@ -284,62 +286,44 @@ pub struct LdapInfo {
|
||||
pub ignored_group_attributes: Vec<AttributeName>,
|
||||
}
|
||||
|
||||
pub fn get_custom_attribute<Extractor: SchemaAttributeExtractor>(
|
||||
pub fn get_custom_attribute(
|
||||
attributes: &[Attribute],
|
||||
attribute_name: &AttributeName,
|
||||
schema: &PublicSchema,
|
||||
) -> Option<Vec<Vec<u8>>> {
|
||||
let convert_date = |date| {
|
||||
chrono::Utc
|
||||
.from_utc_datetime(&date)
|
||||
.from_utc_datetime(date)
|
||||
.to_rfc3339()
|
||||
.into_bytes()
|
||||
};
|
||||
Extractor::get_attributes(schema)
|
||||
.get_attribute_type(attribute_name)
|
||||
.and_then(|attribute_type| {
|
||||
attributes
|
||||
attributes
|
||||
.iter()
|
||||
.find(|a| &a.name == attribute_name)
|
||||
.map(|attribute| match &attribute.value {
|
||||
AttributeValue::String(Cardinality::Singleton(s)) => {
|
||||
vec![s.clone().into_bytes()]
|
||||
}
|
||||
AttributeValue::String(Cardinality::Unbounded(l)) => {
|
||||
l.iter().map(|s| s.clone().into_bytes()).collect()
|
||||
}
|
||||
AttributeValue::Integer(Cardinality::Singleton(i)) => {
|
||||
// LDAP integers are encoded as strings.
|
||||
vec![i.to_string().into_bytes()]
|
||||
}
|
||||
AttributeValue::Integer(Cardinality::Unbounded(l)) => l
|
||||
.iter()
|
||||
.find(|a| &a.name == attribute_name)
|
||||
.map(|attribute| match attribute_type {
|
||||
(AttributeType::String, false) => {
|
||||
vec![attribute.value.unwrap::<String>().into_bytes()]
|
||||
}
|
||||
(AttributeType::Integer, false) => {
|
||||
// LDAP integers are encoded as strings.
|
||||
vec![attribute.value.unwrap::<i64>().to_string().into_bytes()]
|
||||
}
|
||||
(AttributeType::JpegPhoto, false) => {
|
||||
vec![attribute.value.unwrap::<JpegPhoto>().into_bytes()]
|
||||
}
|
||||
(AttributeType::DateTime, false) => {
|
||||
vec![convert_date(attribute.value.unwrap::<NaiveDateTime>())]
|
||||
}
|
||||
(AttributeType::String, true) => attribute
|
||||
.value
|
||||
.unwrap::<Vec<String>>()
|
||||
.into_iter()
|
||||
.map(String::into_bytes)
|
||||
.collect(),
|
||||
(AttributeType::Integer, true) => attribute
|
||||
.value
|
||||
.unwrap::<Vec<i64>>()
|
||||
.into_iter()
|
||||
.map(|i| i.to_string())
|
||||
.map(String::into_bytes)
|
||||
.collect(),
|
||||
(AttributeType::JpegPhoto, true) => attribute
|
||||
.value
|
||||
.unwrap::<Vec<JpegPhoto>>()
|
||||
.into_iter()
|
||||
.map(JpegPhoto::into_bytes)
|
||||
.collect(),
|
||||
(AttributeType::DateTime, true) => attribute
|
||||
.value
|
||||
.unwrap::<Vec<NaiveDateTime>>()
|
||||
.into_iter()
|
||||
.map(convert_date)
|
||||
.collect(),
|
||||
})
|
||||
// LDAP integers are encoded as strings.
|
||||
.map(|i| i.to_string().into_bytes())
|
||||
.collect(),
|
||||
AttributeValue::JpegPhoto(Cardinality::Singleton(p)) => {
|
||||
vec![p.clone().into_bytes()]
|
||||
}
|
||||
AttributeValue::JpegPhoto(Cardinality::Unbounded(l)) => {
|
||||
l.iter().map(|p| p.clone().into_bytes()).collect()
|
||||
}
|
||||
AttributeValue::DateTime(Cardinality::Singleton(dt)) => vec![convert_date(dt)],
|
||||
AttributeValue::DateTime(Cardinality::Unbounded(l)) => {
|
||||
l.iter().map(convert_date).collect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
use crate::domain::error::DomainError;
|
||||
use lldap_domain::{
|
||||
schema::AttributeList,
|
||||
types::{Attribute, AttributeName, AttributeType, AttributeValue, Cardinality, Serialized},
|
||||
};
|
||||
|
||||
// Value must be a serialized attribute value of the type denoted by typ,
|
||||
// and either a singleton or unbounded list, depending on is_list.
|
||||
pub(crate) fn deserialize_attribute_value(
|
||||
value: &Serialized,
|
||||
typ: AttributeType,
|
||||
is_list: bool,
|
||||
) -> AttributeValue {
|
||||
match (typ, is_list) {
|
||||
(AttributeType::String, false) => {
|
||||
AttributeValue::String(Cardinality::Singleton(value.unwrap()))
|
||||
}
|
||||
(AttributeType::String, true) => {
|
||||
AttributeValue::String(Cardinality::Unbounded(value.unwrap()))
|
||||
}
|
||||
(AttributeType::Integer, false) => {
|
||||
AttributeValue::Integer(Cardinality::Singleton(value.unwrap::<i64>()))
|
||||
}
|
||||
(AttributeType::Integer, true) => {
|
||||
AttributeValue::Integer(Cardinality::Unbounded(value.unwrap()))
|
||||
}
|
||||
(AttributeType::DateTime, false) => {
|
||||
AttributeValue::DateTime(Cardinality::Singleton(value.unwrap()))
|
||||
}
|
||||
(AttributeType::DateTime, true) => {
|
||||
AttributeValue::DateTime(Cardinality::Unbounded(value.unwrap()))
|
||||
}
|
||||
(AttributeType::JpegPhoto, false) => {
|
||||
AttributeValue::JpegPhoto(Cardinality::Singleton(value.unwrap()))
|
||||
}
|
||||
(AttributeType::JpegPhoto, true) => {
|
||||
AttributeValue::JpegPhoto(Cardinality::Unbounded(value.unwrap()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_attribute(
|
||||
name: AttributeName,
|
||||
value: &Serialized,
|
||||
schema: &AttributeList,
|
||||
) -> Result<Attribute, DomainError> {
|
||||
match schema.get_attribute_type(&name) {
|
||||
Some((typ, is_list)) => Ok(Attribute {
|
||||
name,
|
||||
value: deserialize_attribute_value(value, typ, is_list),
|
||||
}),
|
||||
None => Err(DomainError::InternalError(format!(
|
||||
"Unable to find schema for attribute named '{}'",
|
||||
name.into_string()
|
||||
))),
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use lldap_domain::types::{Attribute, AttributeName, GroupId, Serialized};
|
||||
use lldap_domain::types::{AttributeName, GroupId, Serialized};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "group_attributes")]
|
||||
@@ -55,18 +55,3 @@ impl Related<super::GroupAttributeSchema> for Entity {
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
impl From<Model> for Attribute {
|
||||
fn from(
|
||||
Model {
|
||||
group_id: _,
|
||||
attribute_name,
|
||||
value,
|
||||
}: Model,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: attribute_name,
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod prelude;
|
||||
|
||||
pub(crate) mod deserialize;
|
||||
pub mod groups;
|
||||
pub mod jwt_refresh_storage;
|
||||
pub mod jwt_storage;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use lldap_domain::types::{Attribute, AttributeName, Serialized, UserId};
|
||||
use lldap_domain::types::{AttributeName, Serialized, UserId};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "user_attributes")]
|
||||
@@ -55,18 +55,3 @@ impl Related<super::UserAttributeSchema> for Entity {
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
impl From<Model> for Attribute {
|
||||
fn from(
|
||||
Model {
|
||||
user_id: _,
|
||||
attribute_name,
|
||||
value,
|
||||
}: Model,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: attribute_name,
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use lldap_domain::{
|
||||
schema::{AttributeList, AttributeSchema, Schema},
|
||||
schema::{AttributeSchema, Schema},
|
||||
types::AttributeType,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -13,26 +13,6 @@ impl PublicSchema {
|
||||
}
|
||||
}
|
||||
|
||||
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(&[
|
||||
|
||||
@@ -33,7 +33,7 @@ pub mod tests {
|
||||
use lldap_auth::{opaque, registration};
|
||||
use lldap_domain::{
|
||||
requests::{CreateGroupRequest, CreateUserRequest},
|
||||
types::{Attribute as DomainAttribute, GroupId, Serialized, UserId},
|
||||
types::{Attribute as DomainAttribute, GroupId, UserId},
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
use sea_orm::Database;
|
||||
@@ -95,11 +95,11 @@ pub mod tests {
|
||||
attributes: vec![
|
||||
DomainAttribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from(("first ".to_string() + name).as_str()),
|
||||
value: ("first ".to_string() + name).into(),
|
||||
},
|
||||
DomainAttribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from(("last ".to_string() + name).as_str()),
|
||||
value: ("last ".to_string() + name).into(),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use crate::domain::{
|
||||
error::{DomainError, Result},
|
||||
handler::{GroupBackendHandler, GroupListerBackendHandler, GroupRequestFilter},
|
||||
model::{self, GroupColumn, MembershipColumn},
|
||||
sql_backend_handler::SqlBackendHandler,
|
||||
use crate::{
|
||||
domain::{
|
||||
error::{DomainError, Result},
|
||||
handler::{GroupBackendHandler, GroupListerBackendHandler, GroupRequestFilter},
|
||||
model::{self, deserialize, GroupColumn, MembershipColumn},
|
||||
sql_backend_handler::SqlBackendHandler,
|
||||
},
|
||||
infra::access_control::UserReadableBackendHandler,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use lldap_domain::{
|
||||
requests::{CreateGroupRequest, UpdateGroupRequest},
|
||||
types::{Attribute, AttributeName, Group, GroupDetails, GroupId, Serialized, Uuid},
|
||||
types::{AttributeName, Group, GroupDetails, GroupId, Serialized, Uuid},
|
||||
};
|
||||
use sea_orm::{
|
||||
sea_query::{Alias, Cond, Expr, Func, IntoCondition, OnConflict, SimpleExpr},
|
||||
@@ -75,7 +78,7 @@ fn get_group_filter_expr(filter: GroupRequestFilter) -> Cond {
|
||||
))))
|
||||
.like(filter.to_sql_filter())
|
||||
.into_condition(),
|
||||
AttributeEquality(name, value) => attribute_condition(name, Some(value)),
|
||||
AttributeEquality(name, value) => attribute_condition(name, Some(value.into())),
|
||||
CustomAttributePresent(name) => attribute_condition(name, None),
|
||||
}
|
||||
}
|
||||
@@ -114,6 +117,8 @@ impl GroupListerBackendHandler for SqlBackendHandler {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// TODO: should be wrapped in a transaction
|
||||
let schema = self.get_schema().await?;
|
||||
let attributes = model::GroupAttributes::find()
|
||||
.filter(
|
||||
model::GroupAttributesColumn::GroupId.in_subquery(
|
||||
@@ -133,8 +138,14 @@ impl GroupListerBackendHandler for SqlBackendHandler {
|
||||
for group in groups.iter_mut() {
|
||||
group.attributes = attributes_iter
|
||||
.take_while_ref(|u| u.group_id == group.id)
|
||||
.map(Attribute::from)
|
||||
.collect();
|
||||
.map(|a| {
|
||||
deserialize::deserialize_attribute(
|
||||
a.attribute_name,
|
||||
&a.value,
|
||||
&schema.get_schema().group_attributes,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
}
|
||||
groups.sort_by(|g1, g2| g1.display_name.cmp(&g2.display_name));
|
||||
Ok(groups)
|
||||
@@ -155,7 +166,17 @@ impl GroupBackendHandler for SqlBackendHandler {
|
||||
.order_by_asc(model::GroupAttributesColumn::AttributeName)
|
||||
.all(&self.sql_pool)
|
||||
.await?;
|
||||
group_details.attributes = attributes.into_iter().map(Attribute::from).collect();
|
||||
let schema = self.get_schema().await?;
|
||||
group_details.attributes = attributes
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
deserialize::deserialize_attribute(
|
||||
a.attribute_name,
|
||||
&a.value,
|
||||
&schema.get_schema().group_attributes,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(group_details)
|
||||
}
|
||||
|
||||
@@ -199,7 +220,7 @@ impl GroupBackendHandler for SqlBackendHandler {
|
||||
new_group_attributes.push(model::group_attributes::ActiveModel {
|
||||
group_id: Set(group_id),
|
||||
attribute_name: Set(attribute.name),
|
||||
value: Set(attribute.value),
|
||||
value: Set(attribute.value.into()),
|
||||
});
|
||||
} else {
|
||||
return Err(DomainError::InternalError(format!(
|
||||
@@ -263,7 +284,7 @@ impl SqlBackendHandler {
|
||||
update_group_attributes.push(model::group_attributes::ActiveModel {
|
||||
group_id: Set(request.group_id),
|
||||
attribute_name: Set(attribute.name.to_owned()),
|
||||
value: Set(attribute.value),
|
||||
value: Set(attribute.value.into()),
|
||||
});
|
||||
} else {
|
||||
return Err(DomainError::InternalError(format!(
|
||||
@@ -319,7 +340,7 @@ mod tests {
|
||||
};
|
||||
use lldap_domain::{
|
||||
requests::CreateAttributeRequest,
|
||||
types::{AttributeType, GroupName, Serialized, UserId},
|
||||
types::{Attribute, AttributeType, GroupName, UserId},
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -449,7 +470,7 @@ mod tests {
|
||||
delete_attributes: Vec::new(),
|
||||
insert_attributes: vec![Attribute {
|
||||
name: "gid".into(),
|
||||
value: Serialized::from(&512),
|
||||
value: 512.into(),
|
||||
}],
|
||||
})
|
||||
.await
|
||||
@@ -459,7 +480,7 @@ mod tests {
|
||||
&fixture.handler,
|
||||
Some(GroupRequestFilter::AttributeEquality(
|
||||
AttributeName::from("gid"),
|
||||
Serialized::from(&512),
|
||||
512.into(),
|
||||
)),
|
||||
)
|
||||
.await,
|
||||
@@ -550,7 +571,7 @@ mod tests {
|
||||
display_name: "New Group".into(),
|
||||
attributes: vec![Attribute {
|
||||
name: "new_attribute".into(),
|
||||
value: Serialized::from("value"),
|
||||
value: "value".to_string().into(),
|
||||
}],
|
||||
})
|
||||
.await
|
||||
@@ -565,7 +586,7 @@ mod tests {
|
||||
group_details.attributes,
|
||||
vec![Attribute {
|
||||
name: "new_attribute".into(),
|
||||
value: Serialized::from("value"),
|
||||
value: "value".to_string().into(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -587,7 +608,7 @@ mod tests {
|
||||
let group_id = fixture.groups[0];
|
||||
let attributes = vec![Attribute {
|
||||
name: "new_attribute".into(),
|
||||
value: Serialized::from(&42i64),
|
||||
value: 42i64.into(),
|
||||
}];
|
||||
fixture
|
||||
.handler
|
||||
|
||||
@@ -181,7 +181,7 @@ mod tests {
|
||||
};
|
||||
use lldap_domain::requests::UpdateUserRequest;
|
||||
use lldap_domain::schema::AttributeList;
|
||||
use lldap_domain::types::{Attribute, AttributeType, Serialized};
|
||||
use lldap_domain::types::{Attribute, AttributeType};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -298,7 +298,7 @@ mod tests {
|
||||
user_id: "bob".into(),
|
||||
insert_attributes: vec![Attribute {
|
||||
name: "new_attribute".into(),
|
||||
value: Serialized::from(&3),
|
||||
value: vec![3].into(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use crate::domain::{
|
||||
error::{DomainError, Result},
|
||||
handler::{UserBackendHandler, UserListerBackendHandler, UserRequestFilter},
|
||||
model::{self, GroupColumn, UserColumn},
|
||||
handler::{
|
||||
ReadSchemaBackendHandler, UserBackendHandler, UserListerBackendHandler, UserRequestFilter,
|
||||
},
|
||||
model::{self, deserialize, GroupColumn, UserColumn},
|
||||
sql_backend_handler::SqlBackendHandler,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use lldap_domain::{
|
||||
requests::{CreateUserRequest, UpdateUserRequest},
|
||||
types::{
|
||||
Attribute, AttributeName, GroupDetails, GroupId, Serialized, User, UserAndGroups, UserId,
|
||||
Uuid,
|
||||
},
|
||||
types::{AttributeName, GroupDetails, GroupId, Serialized, User, UserAndGroups, UserId, Uuid},
|
||||
};
|
||||
use sea_orm::{
|
||||
sea_query::{
|
||||
@@ -83,7 +82,7 @@ fn get_user_filter_expr(filter: UserRequestFilter) -> Cond {
|
||||
ColumnTrait::eq(&column, value).into_condition()
|
||||
}
|
||||
}
|
||||
AttributeEquality(column, value) => attribute_condition(column, Some(value)),
|
||||
AttributeEquality(column, value) => attribute_condition(column, Some(value.into())),
|
||||
MemberOf(group) => user_id_subcondition(
|
||||
Expr::col((group_table, GroupColumn::LowercaseDisplayName))
|
||||
.eq(group.as_str().to_lowercase())
|
||||
@@ -161,12 +160,20 @@ impl UserListerBackendHandler for SqlBackendHandler {
|
||||
.all(&self.sql_pool)
|
||||
.await?;
|
||||
let mut attributes_iter = attributes.into_iter().peekable();
|
||||
// TODO: should be wrapped in a transaction
|
||||
use itertools::Itertools; // For take_while_ref
|
||||
let schema = self.get_schema().await?;
|
||||
for user in users.iter_mut() {
|
||||
user.user.attributes = attributes_iter
|
||||
.take_while_ref(|u| u.user_id == user.user.user_id)
|
||||
.map(Attribute::from)
|
||||
.collect();
|
||||
.map(|a| {
|
||||
deserialize::deserialize_attribute(
|
||||
a.attribute_name,
|
||||
&a.value,
|
||||
&schema.user_attributes,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
}
|
||||
Ok(users)
|
||||
}
|
||||
@@ -208,7 +215,10 @@ impl SqlBackendHandler {
|
||||
.get_attribute_type(&attribute.name)
|
||||
.is_some()
|
||||
{
|
||||
process_serialized(ActiveValue::Set(attribute.value), attribute.name.clone());
|
||||
process_serialized(
|
||||
ActiveValue::Set(attribute.value.into()),
|
||||
attribute.name.clone(),
|
||||
);
|
||||
} else {
|
||||
return Err(DomainError::InternalError(format!(
|
||||
"User attribute name {} doesn't exist in the schema, yet was attempted to be inserted in the database",
|
||||
@@ -270,7 +280,17 @@ impl UserBackendHandler for SqlBackendHandler {
|
||||
.order_by_asc(model::UserAttributesColumn::AttributeName)
|
||||
.all(&self.sql_pool)
|
||||
.await?;
|
||||
user.attributes = attributes.into_iter().map(Attribute::from).collect();
|
||||
let schema = self.get_schema().await?;
|
||||
user.attributes = attributes
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
deserialize::deserialize_attribute(
|
||||
a.attribute_name,
|
||||
&a.value,
|
||||
&schema.user_attributes,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
@@ -317,7 +337,7 @@ impl UserBackendHandler for SqlBackendHandler {
|
||||
new_user_attributes.push(model::user_attributes::ActiveModel {
|
||||
user_id: Set(request.user_id.clone()),
|
||||
attribute_name: Set(attribute.name),
|
||||
value: Set(attribute.value),
|
||||
value: Set(attribute.value.into()),
|
||||
});
|
||||
} else {
|
||||
return Err(DomainError::InternalError(format!(
|
||||
@@ -397,7 +417,7 @@ mod tests {
|
||||
use crate::domain::{
|
||||
handler::SubStringFilter, model::UserColumn, sql_backend_handler::tests::*,
|
||||
};
|
||||
use lldap_domain::types::JpegPhoto;
|
||||
use lldap_domain::types::{Attribute, JpegPhoto};
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -439,7 +459,7 @@ mod tests {
|
||||
&fixture.handler,
|
||||
Some(UserRequestFilter::AttributeEquality(
|
||||
AttributeName::from("first_name"),
|
||||
Serialized::from("first bob"),
|
||||
"first bob".to_string().into(),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
@@ -814,15 +834,15 @@ mod tests {
|
||||
insert_attributes: vec![
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("first_name"),
|
||||
value: "first_name".to_string().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("last_name"),
|
||||
value: "last_name".to_string().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||
value: JpegPhoto::for_tests().into(),
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -841,15 +861,15 @@ mod tests {
|
||||
vec![
|
||||
Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests())
|
||||
value: JpegPhoto::for_tests().into()
|
||||
},
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("first_name")
|
||||
value: "first_name".to_string().into()
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("last_name")
|
||||
value: "last_name".to_string().into()
|
||||
}
|
||||
]
|
||||
);
|
||||
@@ -866,7 +886,7 @@ mod tests {
|
||||
delete_attributes: vec!["last_name".into()],
|
||||
insert_attributes: vec![Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||
value: JpegPhoto::for_tests().into(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
@@ -884,11 +904,11 @@ mod tests {
|
||||
vec![
|
||||
Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests())
|
||||
value: JpegPhoto::for_tests().into()
|
||||
},
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("first bob")
|
||||
value: "first bob".to_string().into()
|
||||
}
|
||||
]
|
||||
);
|
||||
@@ -904,7 +924,7 @@ mod tests {
|
||||
user_id: UserId::new("bob"),
|
||||
insert_attributes: vec![Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("new first"),
|
||||
value: "new first".to_string().into(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
@@ -921,11 +941,11 @@ mod tests {
|
||||
vec![
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("new first")
|
||||
value: "new first".to_string().into()
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("last bob")
|
||||
value: "last bob".to_string().into()
|
||||
}
|
||||
]
|
||||
);
|
||||
@@ -954,7 +974,7 @@ mod tests {
|
||||
user.attributes,
|
||||
vec![Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("last bob")
|
||||
value: "last bob".to_string().into()
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -970,7 +990,7 @@ mod tests {
|
||||
delete_attributes: vec!["first_name".into()],
|
||||
insert_attributes: vec![Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("new first"),
|
||||
value: "new first".to_string().into(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
@@ -987,11 +1007,11 @@ mod tests {
|
||||
vec![
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("new first")
|
||||
value: "new first".to_string().into()
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("last bob")
|
||||
value: "last bob".to_string().into()
|
||||
},
|
||||
]
|
||||
);
|
||||
@@ -1007,7 +1027,7 @@ mod tests {
|
||||
user_id: UserId::new("bob"),
|
||||
insert_attributes: vec![Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||
value: JpegPhoto::for_tests().into(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
@@ -1021,7 +1041,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let avatar = Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||
value: JpegPhoto::for_tests().into(),
|
||||
};
|
||||
assert!(user.attributes.contains(&avatar));
|
||||
fixture
|
||||
@@ -1030,7 +1050,7 @@ mod tests {
|
||||
user_id: UserId::new("bob"),
|
||||
insert_attributes: vec![Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::null()),
|
||||
value: JpegPhoto::null().into(),
|
||||
}],
|
||||
..Default::default()
|
||||
})
|
||||
@@ -1058,15 +1078,15 @@ mod tests {
|
||||
attributes: vec![
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("First Name"),
|
||||
value: "First Name".to_string().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("last_name"),
|
||||
value: "last_name".to_string().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||
value: JpegPhoto::for_tests().into(),
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -1085,15 +1105,15 @@ mod tests {
|
||||
vec![
|
||||
Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests())
|
||||
value: JpegPhoto::for_tests().into()
|
||||
},
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("First Name")
|
||||
value: "First Name".to_string().into()
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("last_name")
|
||||
value: "last_name".to_string().into()
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
@@ -151,7 +151,7 @@ fn unpack_attributes(
|
||||
.cloned()
|
||||
.map(|attr| deserialize_attribute(&schema.get_schema().user_attributes, attr, is_admin))
|
||||
.transpose()?
|
||||
.map(|attr| attr.value.unwrap::<String>())
|
||||
.map(|attr| attr.value.into_string().unwrap())
|
||||
.map(Email::from);
|
||||
let display_name = attributes
|
||||
.iter()
|
||||
@@ -159,7 +159,7 @@ fn unpack_attributes(
|
||||
.cloned()
|
||||
.map(|attr| deserialize_attribute(&schema.get_schema().user_attributes, attr, is_admin))
|
||||
.transpose()?
|
||||
.map(|attr| attr.value.unwrap::<String>());
|
||||
.map(|attr| attr.value.into_string().unwrap());
|
||||
let attributes = attributes
|
||||
.into_iter()
|
||||
.filter(|attr| attr.name != "mail" && attr.name != "display_name")
|
||||
|
||||
@@ -14,10 +14,10 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use anyhow::Context as AnyhowContext;
|
||||
use chrono::{NaiveDateTime, TimeZone};
|
||||
use chrono::TimeZone;
|
||||
use juniper::{graphql_object, FieldResult, GraphQLInputObject};
|
||||
use lldap_domain::types::{
|
||||
AttributeType, GroupDetails, GroupId, JpegPhoto, LdapObjectClass, Serialized, UserId,
|
||||
AttributeType, Cardinality, GroupDetails, GroupId, LdapObjectClass, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, debug_span, Instrument, Span};
|
||||
@@ -29,6 +29,7 @@ type DomainUserAndGroups = lldap_domain::types::UserAndGroups;
|
||||
type DomainAttributeList = lldap_domain::schema::AttributeList;
|
||||
type DomainAttributeSchema = lldap_domain::schema::AttributeSchema;
|
||||
type DomainAttribute = lldap_domain::types::Attribute;
|
||||
type DomainAttributeValue = lldap_domain::types::AttributeValue;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
|
||||
/// A filter for requests, specifying a boolean expression based on field constraints. Only one of
|
||||
@@ -296,23 +297,30 @@ impl<Handler: BackendHandler> User<Handler> {
|
||||
self.attributes
|
||||
.iter()
|
||||
.find(|a| a.attribute.name.as_str() == "first_name")
|
||||
.map(|a| a.attribute.value.unwrap())
|
||||
.unwrap_or("")
|
||||
.map(|a| a.attribute.value.as_str().unwrap_or_default())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn last_name(&self) -> &str {
|
||||
self.attributes
|
||||
.iter()
|
||||
.find(|a| a.attribute.name.as_str() == "last_name")
|
||||
.map(|a| a.attribute.value.unwrap())
|
||||
.unwrap_or("")
|
||||
.map(|a| a.attribute.value.as_str().unwrap_or_default())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn avatar(&self) -> Option<String> {
|
||||
self.attributes
|
||||
.iter()
|
||||
.find(|a| a.attribute.name.as_str() == "avatar")
|
||||
.map(|a| String::from(&a.attribute.value.unwrap::<JpegPhoto>()))
|
||||
.map(|a| {
|
||||
String::from(
|
||||
a.attribute
|
||||
.value
|
||||
.as_jpeg_photo()
|
||||
.expect("Invalid JPEG returned by the DB"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn creation_date(&self) -> chrono::DateTime<chrono::Utc> {
|
||||
@@ -590,7 +598,7 @@ impl<Handler: BackendHandler> AttributeValue<Handler> {
|
||||
}
|
||||
|
||||
fn value(&self) -> FieldResult<Vec<String>> {
|
||||
Ok(serialize_attribute(&self.attribute, &self.schema.schema))
|
||||
Ok(serialize_attribute_to_graphql(&self.attribute.value))
|
||||
}
|
||||
|
||||
fn schema(&self) -> &AttributeSchema<Handler> {
|
||||
@@ -599,9 +607,9 @@ impl<Handler: BackendHandler> AttributeValue<Handler> {
|
||||
}
|
||||
|
||||
impl<Handler: BackendHandler> AttributeValue<Handler> {
|
||||
fn from_domain(value: DomainAttribute, schema: DomainAttributeSchema) -> Self {
|
||||
fn from_value(attr: DomainAttribute, schema: DomainAttributeSchema) -> Self {
|
||||
Self {
|
||||
attribute: value,
|
||||
attribute: attr,
|
||||
schema: AttributeSchema::<Handler> {
|
||||
schema,
|
||||
_phantom: std::marker::PhantomData,
|
||||
@@ -621,46 +629,23 @@ impl<Handler: BackendHandler> Clone for AttributeValue<Handler> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_attribute(
|
||||
attribute: &DomainAttribute,
|
||||
attribute_schema: &DomainAttributeSchema,
|
||||
) -> Vec<String> {
|
||||
let convert_date = |date| chrono::Utc.from_utc_datetime(&date).to_rfc3339();
|
||||
match (attribute_schema.attribute_type, attribute_schema.is_list) {
|
||||
(AttributeType::String, false) => vec![attribute.value.unwrap::<String>()],
|
||||
(AttributeType::Integer, false) => {
|
||||
// LDAP integers are encoded as strings.
|
||||
vec![attribute.value.unwrap::<i64>().to_string()]
|
||||
pub fn serialize_attribute_to_graphql(attribute_value: &DomainAttributeValue) -> Vec<String> {
|
||||
let convert_date = |&date| chrono::Utc.from_utc_datetime(&date).to_rfc3339();
|
||||
match attribute_value {
|
||||
DomainAttributeValue::String(Cardinality::Singleton(s)) => vec![s.clone()],
|
||||
DomainAttributeValue::String(Cardinality::Unbounded(l)) => l.clone(),
|
||||
DomainAttributeValue::Integer(Cardinality::Singleton(i)) => vec![i.to_string()],
|
||||
DomainAttributeValue::Integer(Cardinality::Unbounded(l)) => {
|
||||
l.iter().map(|i| i.to_string()).collect()
|
||||
}
|
||||
(AttributeType::JpegPhoto, false) => {
|
||||
vec![String::from(&attribute.value.unwrap::<JpegPhoto>())]
|
||||
DomainAttributeValue::DateTime(Cardinality::Singleton(dt)) => vec![convert_date(dt)],
|
||||
DomainAttributeValue::DateTime(Cardinality::Unbounded(l)) => {
|
||||
l.iter().map(convert_date).collect()
|
||||
}
|
||||
(AttributeType::DateTime, false) => {
|
||||
vec![convert_date(attribute.value.unwrap::<NaiveDateTime>())]
|
||||
DomainAttributeValue::JpegPhoto(Cardinality::Singleton(p)) => vec![String::from(p)],
|
||||
DomainAttributeValue::JpegPhoto(Cardinality::Unbounded(l)) => {
|
||||
l.iter().map(String::from).collect()
|
||||
}
|
||||
(AttributeType::String, true) => attribute
|
||||
.value
|
||||
.unwrap::<Vec<String>>()
|
||||
.into_iter()
|
||||
.collect(),
|
||||
(AttributeType::Integer, true) => attribute
|
||||
.value
|
||||
.unwrap::<Vec<i64>>()
|
||||
.into_iter()
|
||||
.map(|i| i.to_string())
|
||||
.collect(),
|
||||
(AttributeType::JpegPhoto, true) => attribute
|
||||
.value
|
||||
.unwrap::<Vec<JpegPhoto>>()
|
||||
.iter()
|
||||
.map(String::from)
|
||||
.collect(),
|
||||
(AttributeType::DateTime, true) => attribute
|
||||
.value
|
||||
.unwrap::<Vec<NaiveDateTime>>()
|
||||
.into_iter()
|
||||
.map(convert_date)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +653,7 @@ impl<Handler: BackendHandler> AttributeValue<Handler> {
|
||||
fn from_schema(a: DomainAttribute, schema: &DomainAttributeList) -> Option<Self> {
|
||||
schema
|
||||
.get_attribute_schema(&a.name)
|
||||
.map(|s| AttributeValue::<Handler>::from_domain(a, s.clone()))
|
||||
.map(|s| AttributeValue::<Handler>::from_value(a, s.clone()))
|
||||
}
|
||||
|
||||
fn user_attributes_from_schema(
|
||||
@@ -682,25 +667,25 @@ impl<Handler: BackendHandler> AttributeValue<Handler> {
|
||||
.attributes
|
||||
.iter()
|
||||
.filter(|a| a.is_hardcoded)
|
||||
.flat_map(|attribute| {
|
||||
let value = match attribute.name.as_str() {
|
||||
"user_id" => Some(Serialized::from(&user.user_id)),
|
||||
"creation_date" => Some(Serialized::from(&user.creation_date)),
|
||||
"mail" => Some(Serialized::from(&user.email)),
|
||||
"uuid" => Some(Serialized::from(&user.uuid)),
|
||||
"display_name" => user.display_name.as_ref().map(Serialized::from),
|
||||
.flat_map(|attribute_schema| {
|
||||
let value: Option<DomainAttributeValue> = match attribute_schema.name.as_str() {
|
||||
"user_id" => Some(user.user_id.clone().into_string().into()),
|
||||
"creation_date" => Some(user.creation_date.into()),
|
||||
"mail" => Some(user.email.clone().into_string().into()),
|
||||
"uuid" => Some(user.uuid.clone().into_string().into()),
|
||||
"display_name" => user.display_name.as_ref().map(|d| d.clone().into()),
|
||||
"avatar" | "first_name" | "last_name" => None,
|
||||
_ => panic!("Unexpected hardcoded attribute: {}", attribute.name),
|
||||
_ => panic!("Unexpected hardcoded attribute: {}", attribute_schema.name),
|
||||
};
|
||||
value.map(|v| (attribute, v))
|
||||
value.map(|v| (attribute_schema, v))
|
||||
})
|
||||
.map(|(attribute, value)| {
|
||||
AttributeValue::<Handler>::from_domain(
|
||||
.map(|(attribute_schema, value)| {
|
||||
AttributeValue::<Handler>::from_value(
|
||||
DomainAttribute {
|
||||
name: attribute.name.clone(),
|
||||
name: attribute_schema.name.clone(),
|
||||
value,
|
||||
},
|
||||
attribute.clone(),
|
||||
attribute_schema.clone(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -724,25 +709,25 @@ impl<Handler: BackendHandler> AttributeValue<Handler> {
|
||||
.attributes
|
||||
.iter()
|
||||
.filter(|a| a.is_hardcoded)
|
||||
.map(|attribute| {
|
||||
.map(|attribute_schema| {
|
||||
(
|
||||
attribute,
|
||||
match attribute.name.as_str() {
|
||||
"group_id" => Serialized::from(&(group.id.0 as i64)),
|
||||
"creation_date" => Serialized::from(&group.creation_date),
|
||||
"uuid" => Serialized::from(&group.uuid),
|
||||
"display_name" => Serialized::from(&group.display_name),
|
||||
_ => panic!("Unexpected hardcoded attribute: {}", attribute.name),
|
||||
attribute_schema,
|
||||
match attribute_schema.name.as_str() {
|
||||
"group_id" => (group.id.0 as i64).into(),
|
||||
"creation_date" => group.creation_date.into(),
|
||||
"uuid" => group.uuid.clone().into_string().into(),
|
||||
"display_name" => group.display_name.clone().into_string().into(),
|
||||
_ => panic!("Unexpected hardcoded attribute: {}", attribute_schema.name),
|
||||
},
|
||||
)
|
||||
})
|
||||
.map(|(attribute, value)| {
|
||||
AttributeValue::<Handler>::from_domain(
|
||||
.map(|(attribute_schema, value)| {
|
||||
AttributeValue::<Handler>::from_value(
|
||||
DomainAttribute {
|
||||
name: attribute.name.clone(),
|
||||
name: attribute_schema.name.clone(),
|
||||
value,
|
||||
},
|
||||
attribute.clone(),
|
||||
attribute_schema.clone(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -766,25 +751,25 @@ impl<Handler: BackendHandler> AttributeValue<Handler> {
|
||||
.attributes
|
||||
.iter()
|
||||
.filter(|a| a.is_hardcoded)
|
||||
.map(|attribute| {
|
||||
.map(|attribute_schema| {
|
||||
(
|
||||
attribute,
|
||||
match attribute.name.as_str() {
|
||||
"group_id" => Serialized::from(&(group.group_id.0 as i64)),
|
||||
"creation_date" => Serialized::from(&group.creation_date),
|
||||
"uuid" => Serialized::from(&group.uuid),
|
||||
"display_name" => Serialized::from(&group.display_name),
|
||||
_ => panic!("Unexpected hardcoded attribute: {}", attribute.name),
|
||||
attribute_schema,
|
||||
match attribute_schema.name.as_str() {
|
||||
"group_id" => (group.group_id.0 as i64).into(),
|
||||
"creation_date" => group.creation_date.into(),
|
||||
"uuid" => group.uuid.clone().into_string().into(),
|
||||
"display_name" => group.display_name.clone().into_string().into(),
|
||||
_ => panic!("Unexpected hardcoded attribute: {}", attribute_schema.name),
|
||||
},
|
||||
)
|
||||
})
|
||||
.map(|(attribute, value)| {
|
||||
AttributeValue::<Handler>::from_domain(
|
||||
.map(|(attribute_schema, value)| {
|
||||
AttributeValue::<Handler>::from_value(
|
||||
DomainAttribute {
|
||||
name: attribute.name.clone(),
|
||||
name: attribute_schema.name.clone(),
|
||||
value,
|
||||
},
|
||||
attribute.clone(),
|
||||
attribute_schema.clone(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -812,7 +797,7 @@ mod tests {
|
||||
};
|
||||
use lldap_domain::{
|
||||
schema::{AttributeList, Schema},
|
||||
types::{AttributeName, AttributeType, LdapObjectClass, Serialized},
|
||||
types::{AttributeName, AttributeType, LdapObjectClass},
|
||||
};
|
||||
use mockall::predicate::eq;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -910,11 +895,11 @@ mod tests {
|
||||
attributes: vec![
|
||||
DomainAttribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("Bob"),
|
||||
value: "Bob".to_string().into(),
|
||||
},
|
||||
DomainAttribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("Bobberson"),
|
||||
value: "Bobberson".to_string().into(),
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
@@ -928,7 +913,7 @@ mod tests {
|
||||
uuid: lldap_domain::uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||
attributes: vec![DomainAttribute {
|
||||
name: "club_name".into(),
|
||||
value: Serialized::from("Gang of Four"),
|
||||
value: "Gang of Four".to_string().into(),
|
||||
}],
|
||||
});
|
||||
groups.insert(GroupDetails {
|
||||
@@ -1074,7 +1059,7 @@ mod tests {
|
||||
),
|
||||
DomainRequestFilter::AttributeEquality(
|
||||
AttributeName::from("first_name"),
|
||||
Serialized::from("robert"),
|
||||
"robert".to_string().into(),
|
||||
),
|
||||
]))),
|
||||
eq(false),
|
||||
|
||||
@@ -1300,11 +1300,11 @@ mod tests {
|
||||
attributes: vec![
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("Bôb"),
|
||||
value: "Bôb".to_string().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("Böbberson"),
|
||||
value: "Böbberson".to_string().into(),
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
@@ -1319,15 +1319,15 @@ mod tests {
|
||||
attributes: vec![
|
||||
Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||
value: JpegPhoto::for_tests().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("Jim"),
|
||||
value: "Jim".to_string().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("Cricket"),
|
||||
value: "Cricket".to_string().into(),
|
||||
},
|
||||
],
|
||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||
@@ -1720,11 +1720,11 @@ mod tests {
|
||||
.with(eq(Some(GroupRequestFilter::Or(vec![
|
||||
GroupRequestFilter::AttributeEquality(
|
||||
AttributeName::from("attr"),
|
||||
Serialized::from("TEST"),
|
||||
"TEST".to_string().into(),
|
||||
),
|
||||
GroupRequestFilter::AttributeEquality(
|
||||
AttributeName::from("attr"),
|
||||
Serialized::from("test"),
|
||||
"test".to_string().into(),
|
||||
),
|
||||
]))))
|
||||
.times(1)
|
||||
@@ -1737,7 +1737,7 @@ mod tests {
|
||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||
attributes: vec![Attribute {
|
||||
name: "Attr".into(),
|
||||
value: Serialized::from("TEST"),
|
||||
value: "TEST".to_string().into(),
|
||||
}],
|
||||
}])
|
||||
});
|
||||
@@ -1914,11 +1914,11 @@ mod tests {
|
||||
UserRequestFilter::Or(vec![
|
||||
UserRequestFilter::AttributeEquality(
|
||||
AttributeName::from("first_name"),
|
||||
Serialized::from("FirstName"),
|
||||
"FirstName".to_string().into(),
|
||||
),
|
||||
UserRequestFilter::AttributeEquality(
|
||||
AttributeName::from("first_name"),
|
||||
Serialized::from("firstname"),
|
||||
"firstname".to_string().into(),
|
||||
),
|
||||
]),
|
||||
false.into(),
|
||||
@@ -2173,11 +2173,11 @@ mod tests {
|
||||
attributes: vec![
|
||||
Attribute {
|
||||
name: "first_name".into(),
|
||||
value: Serialized::from("Bôb"),
|
||||
value: "Bôb".to_string().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("Böbberson"),
|
||||
name: "last_name".to_string().into(),
|
||||
value: "Böbberson".to_string().into(),
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
@@ -2257,11 +2257,11 @@ mod tests {
|
||||
attributes: vec![
|
||||
Attribute {
|
||||
name: "avatar".into(),
|
||||
value: Serialized::from(&JpegPhoto::for_tests()),
|
||||
value: JpegPhoto::for_tests().into(),
|
||||
},
|
||||
Attribute {
|
||||
name: "last_name".into(),
|
||||
value: Serialized::from("Böbberson"),
|
||||
value: "Böbberson".to_string().into(),
|
||||
},
|
||||
],
|
||||
uuid: uuid!("b4ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||
@@ -3078,7 +3078,7 @@ mod tests {
|
||||
user_id: UserId::new("test"),
|
||||
attributes: vec![Attribute {
|
||||
name: "nickname".into(),
|
||||
value: Serialized::from("Bob the Builder"),
|
||||
value: "Bob the Builder".to_string().into(),
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -3094,7 +3094,7 @@ mod tests {
|
||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||
attributes: vec![Attribute {
|
||||
name: "club_name".into(),
|
||||
value: Serialized::from("Breakfast Club"),
|
||||
value: "Breakfast Club".to_string().into(),
|
||||
}],
|
||||
}])
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user