server: Add support for custom LDAP object classes for users and groups

This commit is contained in:
Valentin Tolmer
2024-02-05 22:20:08 +01:00
committed by nitnelave
parent fa9743be6a
commit 646fe32645
15 changed files with 323 additions and 29 deletions
+1
View File
@@ -162,6 +162,7 @@ enum AttributeType {
type AttributeList { type AttributeList {
attributes: [AttributeSchema!]! attributes: [AttributeSchema!]!
extraLdapObjectClasses: [String!]!
} }
type Success { type Success {
+9 -1
View File
@@ -2,7 +2,8 @@ use crate::domain::{
error::Result, error::Result,
types::{ types::{
AttributeName, AttributeType, AttributeValue, Email, Group, GroupDetails, GroupId, AttributeName, AttributeType, AttributeValue, Email, Group, GroupDetails, GroupId,
GroupName, JpegPhoto, Serialized, User, UserAndGroups, UserColumn, UserId, Uuid, GroupName, JpegPhoto, LdapObjectClass, Serialized, User, UserAndGroups, UserColumn, UserId,
Uuid,
}, },
}; };
use async_trait::async_trait; use async_trait::async_trait;
@@ -175,6 +176,8 @@ impl AttributeList {
pub struct Schema { pub struct Schema {
pub user_attributes: AttributeList, pub user_attributes: AttributeList,
pub group_attributes: AttributeList, pub group_attributes: AttributeList,
pub extra_user_object_classes: Vec<LdapObjectClass>,
pub extra_group_object_classes: Vec<LdapObjectClass>,
} }
#[async_trait] #[async_trait]
@@ -227,6 +230,11 @@ pub trait SchemaBackendHandler: ReadSchemaBackendHandler {
// Note: It's up to the caller to make sure that the attribute is not hardcoded. // Note: It's up to the caller to make sure that the attribute is not hardcoded.
async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()>; async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()>;
async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()>; async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()>;
async fn add_user_object_class(&self, name: &LdapObjectClass) -> Result<()>;
async fn add_group_object_class(&self, name: &LdapObjectClass) -> Result<()>;
async fn delete_user_object_class(&self, name: &LdapObjectClass) -> Result<()>;
async fn delete_group_object_class(&self, name: &LdapObjectClass) -> Result<()>;
} }
#[async_trait] #[async_trait]
+11 -1
View File
@@ -30,7 +30,17 @@ pub fn get_group_attribute(
) -> Option<Vec<Vec<u8>>> { ) -> Option<Vec<Vec<u8>>> {
let attribute = AttributeName::from(attribute); let attribute = AttributeName::from(attribute);
let attribute_values = match map_group_field(&attribute, schema) { let attribute_values = match map_group_field(&attribute, schema) {
GroupFieldType::ObjectClass => vec![b"groupOfUniqueNames".to_vec()], GroupFieldType::ObjectClass => {
let mut classes = vec![b"groupOfUniqueNames".to_vec()];
classes.extend(
schema
.get_schema()
.extra_group_object_classes
.iter()
.map(|c| c.as_str().as_bytes().to_vec()),
);
classes
}
// Always returned as part of the base response. // Always returned as part of the base response.
GroupFieldType::Dn => return None, GroupFieldType::Dn => return None,
GroupFieldType::EntryDn => { GroupFieldType::EntryDn => {
+16 -6
View File
@@ -28,12 +28,22 @@ pub fn get_user_attribute(
) -> Option<Vec<Vec<u8>>> { ) -> Option<Vec<Vec<u8>>> {
let attribute = AttributeName::from(attribute); let attribute = AttributeName::from(attribute);
let attribute_values = match map_user_field(&attribute, schema) { let attribute_values = match map_user_field(&attribute, schema) {
UserFieldType::ObjectClass => vec![ UserFieldType::ObjectClass => {
b"inetOrgPerson".to_vec(), let mut classes = vec![
b"posixAccount".to_vec(), b"inetOrgPerson".to_vec(),
b"mailAccount".to_vec(), b"posixAccount".to_vec(),
b"person".to_vec(), b"mailAccount".to_vec(),
], b"person".to_vec(),
];
classes.extend(
schema
.get_schema()
.extra_user_object_classes
.iter()
.map(|c| c.as_str().as_bytes().to_vec()),
);
classes
}
// dn is always returned as part of the base response. // dn is always returned as part of the base response.
UserFieldType::Dn => return None, UserFieldType::Dn => return None,
UserFieldType::EntryDn => { UserFieldType::EntryDn => {
@@ -0,0 +1,23 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use crate::domain::types::LdapObjectClass;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "group_object_classes")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub lower_object_class: String,
pub object_class: LdapObjectClass,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
impl From<Model> for LdapObjectClass {
fn from(value: Model) -> Self {
value.object_class
}
}
+2 -2
View File
@@ -1,5 +1,3 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.3
pub mod prelude; pub mod prelude;
pub mod groups; pub mod groups;
@@ -11,8 +9,10 @@ pub mod users;
pub mod user_attribute_schema; pub mod user_attribute_schema;
pub mod user_attributes; pub mod user_attributes;
pub mod user_object_classes;
pub mod group_attribute_schema; pub mod group_attribute_schema;
pub mod group_attributes; pub mod group_attributes;
pub mod group_object_classes;
pub use prelude::*; pub use prelude::*;
+4
View File
@@ -4,6 +4,8 @@ pub use super::group_attribute_schema::Column as GroupAttributeSchemaColumn;
pub use super::group_attribute_schema::Entity as GroupAttributeSchema; pub use super::group_attribute_schema::Entity as GroupAttributeSchema;
pub use super::group_attributes::Column as GroupAttributesColumn; pub use super::group_attributes::Column as GroupAttributesColumn;
pub use super::group_attributes::Entity as GroupAttributes; pub use super::group_attributes::Entity as GroupAttributes;
pub use super::group_object_classes::Column as GroupObjectClassesColumn;
pub use super::group_object_classes::Entity as GroupObjectClasses;
pub use super::groups::Column as GroupColumn; pub use super::groups::Column as GroupColumn;
pub use super::groups::Entity as Group; pub use super::groups::Entity as Group;
pub use super::jwt_refresh_storage::Column as JwtRefreshStorageColumn; pub use super::jwt_refresh_storage::Column as JwtRefreshStorageColumn;
@@ -18,5 +20,7 @@ pub use super::user_attribute_schema::Column as UserAttributeSchemaColumn;
pub use super::user_attribute_schema::Entity as UserAttributeSchema; pub use super::user_attribute_schema::Entity as UserAttributeSchema;
pub use super::user_attributes::Column as UserAttributesColumn; pub use super::user_attributes::Column as UserAttributesColumn;
pub use super::user_attributes::Entity as UserAttributes; pub use super::user_attributes::Entity as UserAttributes;
pub use super::user_object_classes::Column as UserObjectClassesColumn;
pub use super::user_object_classes::Entity as UserObjectClasses;
pub use super::users::Column as UserColumn; pub use super::users::Column as UserColumn;
pub use super::users::Entity as User; pub use super::users::Entity as User;
@@ -0,0 +1,23 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use crate::domain::types::LdapObjectClass;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user_object_classes")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub lower_object_class: String,
pub object_class: LdapObjectClass,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
impl From<Model> for LdapObjectClass {
fn from(value: Model) -> Self {
value.object_class
}
}
+60
View File
@@ -88,6 +88,20 @@ pub enum GroupAttributes {
GroupAttributeValue, GroupAttributeValue,
} }
#[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
pub enum UserObjectClasses {
Table,
LowerObjectClass,
ObjectClass,
}
#[derive(DeriveIden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
pub enum GroupObjectClasses {
Table,
LowerObjectClass,
ObjectClass,
}
// Metadata about the SQL DB. // Metadata about the SQL DB.
#[derive(DeriveIden)] #[derive(DeriveIden)]
pub enum Metadata { pub enum Metadata {
@@ -1031,6 +1045,51 @@ async fn migrate_to_v8(transaction: DatabaseTransaction) -> Result<DatabaseTrans
Ok(transaction) Ok(transaction)
} }
async fn migrate_to_v9(transaction: DatabaseTransaction) -> Result<DatabaseTransaction, DbErr> {
let builder = transaction.get_database_backend();
transaction
.execute(
builder.build(
Table::create()
.table(UserObjectClasses::Table)
.if_not_exists()
.col(
ColumnDef::new(UserObjectClasses::LowerObjectClass)
.string_len(255)
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(UserObjectClasses::ObjectClass)
.string_len(255)
.not_null(),
),
),
)
.await?;
transaction
.execute(
builder.build(
Table::create()
.table(GroupObjectClasses::Table)
.if_not_exists()
.col(
ColumnDef::new(GroupObjectClasses::LowerObjectClass)
.string_len(255)
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(GroupObjectClasses::ObjectClass)
.string_len(255)
.not_null(),
),
),
)
.await?;
Ok(transaction)
}
// This is needed to make an array of async functions. // This is needed to make an array of async functions.
macro_rules! to_sync { macro_rules! to_sync {
($l:ident) => { ($l:ident) => {
@@ -1059,6 +1118,7 @@ pub async fn migrate_from_version(
to_sync!(migrate_to_v6), to_sync!(migrate_to_v6),
to_sync!(migrate_to_v7), to_sync!(migrate_to_v7),
to_sync!(migrate_to_v8), to_sync!(migrate_to_v8),
to_sync!(migrate_to_v9),
]; ];
assert_eq!(migrations.len(), (LAST_SCHEMA_VERSION.0 - 1) as usize); assert_eq!(migrations.len(), (LAST_SCHEMA_VERSION.0 - 1) as usize);
for migration in 2..=last_version.0 { for migration in 2..=last_version.0 {
+114 -2
View File
@@ -6,7 +6,7 @@ use crate::domain::{
}, },
model, model,
sql_backend_handler::SqlBackendHandler, sql_backend_handler::SqlBackendHandler,
types::AttributeName, types::{AttributeName, LdapObjectClass},
}; };
use async_trait::async_trait; use async_trait::async_trait;
use sea_orm::{ use sea_orm::{
@@ -66,6 +66,44 @@ impl SchemaBackendHandler for SqlBackendHandler {
.await?; .await?;
Ok(()) Ok(())
} }
async fn add_user_object_class(&self, name: &LdapObjectClass) -> Result<()> {
let mut name_key = name.to_string();
name_key.make_ascii_lowercase();
model::user_object_classes::ActiveModel {
lower_object_class: Set(name_key),
object_class: Set(name.clone()),
}
.insert(&self.sql_pool)
.await?;
Ok(())
}
async fn add_group_object_class(&self, name: &LdapObjectClass) -> Result<()> {
let mut name_key = name.to_string();
name_key.make_ascii_lowercase();
model::group_object_classes::ActiveModel {
lower_object_class: Set(name_key),
object_class: Set(name.clone()),
}
.insert(&self.sql_pool)
.await?;
Ok(())
}
async fn delete_user_object_class(&self, name: &LdapObjectClass) -> Result<()> {
model::UserObjectClasses::delete_by_id(name.as_str().to_ascii_lowercase())
.exec(&self.sql_pool)
.await?;
Ok(())
}
async fn delete_group_object_class(&self, name: &LdapObjectClass) -> Result<()> {
model::GroupObjectClasses::delete_by_id(name.as_str().to_ascii_lowercase())
.exec(&self.sql_pool)
.await?;
Ok(())
}
} }
impl SqlBackendHandler { impl SqlBackendHandler {
@@ -79,6 +117,8 @@ impl SqlBackendHandler {
group_attributes: AttributeList { group_attributes: AttributeList {
attributes: Self::get_group_attributes(transaction).await?, attributes: Self::get_group_attributes(transaction).await?,
}, },
extra_user_object_classes: Self::get_user_object_classes(transaction).await?,
extra_group_object_classes: Self::get_group_object_classes(transaction).await?,
}) })
} }
@@ -105,6 +145,30 @@ impl SqlBackendHandler {
.map(|m| m.into()) .map(|m| m.into())
.collect()) .collect())
} }
async fn get_user_object_classes(
transaction: &DatabaseTransaction,
) -> Result<Vec<LdapObjectClass>> {
Ok(model::UserObjectClasses::find()
.order_by_asc(model::UserObjectClassesColumn::ObjectClass)
.all(transaction)
.await?
.into_iter()
.map(Into::into)
.collect())
}
async fn get_group_object_classes(
transaction: &DatabaseTransaction,
) -> Result<Vec<LdapObjectClass>> {
Ok(model::GroupObjectClasses::find()
.order_by_asc(model::GroupObjectClassesColumn::ObjectClass)
.all(transaction)
.await?
.into_iter()
.map(Into::into)
.collect())
}
} }
#[cfg(test)] #[cfg(test)]
@@ -151,7 +215,9 @@ mod tests {
}, },
group_attributes: AttributeList { group_attributes: AttributeList {
attributes: Vec::new() attributes: Vec::new()
} },
extra_user_object_classes: Vec::new(),
extra_group_object_classes: Vec::new(),
} }
); );
} }
@@ -247,4 +313,50 @@ mod tests {
.attributes .attributes
.contains(&expected_value)); .contains(&expected_value));
} }
#[tokio::test]
async fn test_user_object_class_add_and_delete() {
let fixture = TestFixture::new().await;
let new_object_class = LdapObjectClass::new("newObjectClass");
fixture
.handler
.add_user_object_class(&new_object_class)
.await
.unwrap();
assert_eq!(
fixture
.handler
.get_schema()
.await
.unwrap()
.extra_user_object_classes,
vec![new_object_class.clone()]
);
fixture
.handler
.add_user_object_class(&LdapObjectClass::new("newobjEctclass"))
.await
.expect_err("Should not be able to add the same object class twice");
assert_eq!(
fixture
.handler
.get_schema()
.await
.unwrap()
.extra_user_object_classes,
vec![new_object_class.clone()]
);
fixture
.handler
.delete_user_object_class(&new_object_class)
.await
.unwrap();
assert!(fixture
.handler
.get_schema()
.await
.unwrap()
.extra_user_object_classes
.is_empty());
}
} }
+1 -1
View File
@@ -11,7 +11,7 @@ pub type DbConnection = sea_orm::DatabaseConnection;
#[derive(Copy, PartialEq, Eq, Debug, Clone, PartialOrd, Ord, DeriveValueType)] #[derive(Copy, PartialEq, Eq, Debug, Clone, PartialOrd, Ord, DeriveValueType)]
pub struct SchemaVersion(pub i16); pub struct SchemaVersion(pub i16);
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(8); pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(9);
#[derive(Copy, PartialEq, Eq, Debug, Clone, PartialOrd, Ord)] #[derive(Copy, PartialEq, Eq, Debug, Clone, PartialOrd, Ord)]
pub struct PrivateKeyHash(pub [u8; 32]); pub struct PrivateKeyHash(pub [u8; 32]);
+2
View File
@@ -271,6 +271,8 @@ impl TryFromU64 for AttributeName {
)) ))
} }
} }
make_case_insensitive_comparable_string!(LdapObjectClass);
make_case_insensitive_comparable_string!(Email); make_case_insensitive_comparable_string!(Email);
make_case_insensitive_comparable_string!(GroupName); make_case_insensitive_comparable_string!(GroupName);
+37 -12
View File
@@ -7,7 +7,7 @@ use crate::{
ldap::utils::{map_user_field, UserFieldType}, ldap::utils::{map_user_field, UserFieldType},
model::UserColumn, model::UserColumn,
schema::PublicSchema, schema::PublicSchema,
types::{AttributeType, GroupDetails, GroupId, JpegPhoto, UserId}, types::{AttributeType, GroupDetails, GroupId, JpegPhoto, LdapObjectClass, UserId},
}, },
infra::{ infra::{
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler}, access_control::{ReadonlyBackendHandler, UserReadableBackendHandler},
@@ -523,26 +523,32 @@ impl<Handler: BackendHandler> From<DomainAttributeSchema> for AttributeSchema<Ha
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct AttributeList<Handler: BackendHandler> { pub struct AttributeList<Handler: BackendHandler> {
schema: DomainAttributeList, attributes: DomainAttributeList,
extra_classes: Vec<LdapObjectClass>,
_phantom: std::marker::PhantomData<Box<Handler>>, _phantom: std::marker::PhantomData<Box<Handler>>,
} }
#[graphql_object(context = Context<Handler>)] #[graphql_object(context = Context<Handler>)]
impl<Handler: BackendHandler> AttributeList<Handler> { impl<Handler: BackendHandler> AttributeList<Handler> {
fn attributes(&self) -> Vec<AttributeSchema<Handler>> { fn attributes(&self) -> Vec<AttributeSchema<Handler>> {
self.schema self.attributes
.attributes .attributes
.clone() .clone()
.into_iter() .into_iter()
.map(Into::into) .map(Into::into)
.collect() .collect()
} }
fn extra_ldap_object_classes(&self) -> Vec<String> {
self.extra_classes.iter().map(|c| c.to_string()).collect()
}
} }
impl<Handler: BackendHandler> From<DomainAttributeList> for AttributeList<Handler> { impl<Handler: BackendHandler> AttributeList<Handler> {
fn from(value: DomainAttributeList) -> Self { fn new(attributes: DomainAttributeList, extra_classes: Vec<LdapObjectClass>) -> Self {
Self { Self {
schema: value, attributes,
extra_classes,
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
} }
} }
@@ -557,10 +563,16 @@ pub struct Schema<Handler: BackendHandler> {
#[graphql_object(context = Context<Handler>)] #[graphql_object(context = Context<Handler>)]
impl<Handler: BackendHandler> Schema<Handler> { impl<Handler: BackendHandler> Schema<Handler> {
fn user_schema(&self) -> AttributeList<Handler> { fn user_schema(&self) -> AttributeList<Handler> {
self.schema.get_schema().user_attributes.clone().into() AttributeList::<Handler>::new(
self.schema.get_schema().user_attributes.clone(),
self.schema.get_schema().extra_user_object_classes.clone(),
)
} }
fn group_schema(&self) -> AttributeList<Handler> { fn group_schema(&self) -> AttributeList<Handler> {
self.schema.get_schema().group_attributes.clone().into() AttributeList::<Handler>::new(
self.schema.get_schema().group_attributes.clone(),
self.schema.get_schema().extra_group_object_classes.clone(),
)
} }
} }
@@ -670,7 +682,7 @@ mod tests {
use crate::{ use crate::{
domain::{ domain::{
handler::AttributeList, handler::AttributeList,
types::{AttributeName, AttributeType, Serialized}, types::{AttributeName, AttributeType, LdapObjectClass, Serialized},
}, },
infra::{ infra::{
access_control::{Permission, ValidationResults}, access_control::{Permission, ValidationResults},
@@ -755,6 +767,11 @@ mod tests {
is_hardcoded: false, is_hardcoded: false,
}], }],
}, },
extra_user_object_classes: vec![
LdapObjectClass::from("customUserClass"),
LdapObjectClass::from("myUserClass"),
],
extra_group_object_classes: vec![LdapObjectClass::from("customGroupClass")],
}) })
}); });
mock.expect_get_user_details() mock.expect_get_user_details()
@@ -946,6 +963,7 @@ mod tests {
isEditable isEditable
isHardcoded isHardcoded
} }
extraLdapObjectClasses
} }
groupSchema { groupSchema {
attributes { attributes {
@@ -956,6 +974,7 @@ mod tests {
isEditable isEditable
isHardcoded isHardcoded
} }
extraLdapObjectClasses
} }
} }
}"#; }"#;
@@ -1040,7 +1059,8 @@ mod tests {
"isEditable": false, "isEditable": false,
"isHardcoded": true, "isHardcoded": true,
}, },
] ],
"extraLdapObjectClasses": ["customUserClass"],
}, },
"groupSchema": { "groupSchema": {
"attributes": [ "attributes": [
@@ -1076,7 +1096,8 @@ mod tests {
"isEditable": false, "isEditable": false,
"isHardcoded": true, "isHardcoded": true,
}, },
] ],
"extraLdapObjectClasses": [],
} }
} }
}), }),
@@ -1093,6 +1114,7 @@ mod tests {
attributes { attributes {
name name
} }
extraLdapObjectClasses
} }
} }
}"#; }"#;
@@ -1114,6 +1136,8 @@ mod tests {
group_attributes: AttributeList { group_attributes: AttributeList {
attributes: Vec::new(), attributes: Vec::new(),
}, },
extra_user_object_classes: vec![LdapObjectClass::from("customUserClass")],
extra_group_object_classes: Vec::new(),
}) })
}); });
@@ -1139,7 +1163,8 @@ mod tests {
{"name": "mail"}, {"name": "mail"},
{"name": "user_id"}, {"name": "user_id"},
{"name": "uuid"}, {"name": "uuid"},
] ],
"extraLdapObjectClasses": ["customUserClass"],
} }
} }
} ), } ),
+14 -4
View File
@@ -1290,7 +1290,8 @@ mod tests {
b"inetOrgPerson".to_vec(), b"inetOrgPerson".to_vec(),
b"posixAccount".to_vec(), b"posixAccount".to_vec(),
b"mailAccount".to_vec(), b"mailAccount".to_vec(),
b"person".to_vec() b"person".to_vec(),
b"customUserClass".to_vec(),
] ]
}, },
LdapPartialAttribute { LdapPartialAttribute {
@@ -1332,7 +1333,8 @@ mod tests {
b"inetOrgPerson".to_vec(), b"inetOrgPerson".to_vec(),
b"posixAccount".to_vec(), b"posixAccount".to_vec(),
b"mailAccount".to_vec(), b"mailAccount".to_vec(),
b"person".to_vec() b"person".to_vec(),
b"customUserClass".to_vec(),
] ]
}, },
LdapPartialAttribute { LdapPartialAttribute {
@@ -1919,7 +1921,8 @@ mod tests {
b"inetOrgPerson".to_vec(), b"inetOrgPerson".to_vec(),
b"posixAccount".to_vec(), b"posixAccount".to_vec(),
b"mailAccount".to_vec(), b"mailAccount".to_vec(),
b"person".to_vec() b"person".to_vec(),
b"customUserClass".to_vec(),
] ]
},] },]
}), }),
@@ -1983,7 +1986,8 @@ mod tests {
b"inetOrgPerson".to_vec(), b"inetOrgPerson".to_vec(),
b"posixAccount".to_vec(), b"posixAccount".to_vec(),
b"mailAccount".to_vec(), b"mailAccount".to_vec(),
b"person".to_vec() b"person".to_vec(),
b"customUserClass".to_vec(),
] ]
}, },
LdapPartialAttribute { LdapPartialAttribute {
@@ -2068,6 +2072,7 @@ mod tests {
b"posixAccount".to_vec(), b"posixAccount".to_vec(),
b"mailAccount".to_vec(), b"mailAccount".to_vec(),
b"person".to_vec(), b"person".to_vec(),
b"customUserClass".to_vec(),
], ],
}, },
LdapPartialAttribute { LdapPartialAttribute {
@@ -2849,6 +2854,11 @@ mod tests {
is_hardcoded: false, is_hardcoded: false,
}], }],
}, },
extra_user_object_classes: vec![
LdapObjectClass::from("customUserClass"),
LdapObjectClass::from("myUserClass"),
],
extra_group_object_classes: vec![LdapObjectClass::from("customGroupClass")],
}) })
}); });
let mut ldap_handler = setup_bound_readonly_handler(mock).await; let mut ldap_handler = setup_bound_readonly_handler(mock).await;
+6
View File
@@ -47,6 +47,10 @@ mockall::mock! {
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>; async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()>;
async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()>; async fn delete_user_attribute(&self, name: &AttributeName) -> Result<()>;
async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()>; async fn delete_group_attribute(&self, name: &AttributeName) -> Result<()>;
async fn add_user_object_class(&self, request: &LdapObjectClass) -> Result<()>;
async fn add_group_object_class(&self, request: &LdapObjectClass) -> Result<()>;
async fn delete_user_object_class(&self, name: &LdapObjectClass) -> Result<()>;
async fn delete_group_object_class(&self, name: &LdapObjectClass) -> Result<()>;
} }
#[async_trait] #[async_trait]
impl BackendHandler for TestBackendHandler {} impl BackendHandler for TestBackendHandler {}
@@ -102,6 +106,8 @@ pub fn setup_default_schema(mock: &mut MockTestBackendHandler) {
group_attributes: AttributeList { group_attributes: AttributeList {
attributes: Vec::new(), attributes: Vec::new(),
}, },
extra_user_object_classes: vec![LdapObjectClass::from("customUserClass")],
extra_group_object_classes: Vec::new(),
}) })
}); });
} }