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