server: Use schema to populate attributes

This commit is contained in:
Valentin Tolmer
2023-06-28 17:13:09 +02:00
committed by nitnelave
parent 829ebf59f7
commit 3140af63de
13 changed files with 429 additions and 135 deletions

View File

@@ -6,8 +6,9 @@ use tracing::info;
use crate::domain::{
error::Result,
handler::{
BackendHandler, CreateUserRequest, GroupListerBackendHandler, GroupRequestFilter,
UpdateGroupRequest, UpdateUserRequest, UserListerBackendHandler, UserRequestFilter,
BackendHandler, CreateUserRequest, GroupBackendHandler, GroupListerBackendHandler,
GroupRequestFilter, Schema, SchemaBackendHandler, UpdateGroupRequest, UpdateUserRequest,
UserBackendHandler, UserListerBackendHandler, UserRequestFilter,
},
types::{Group, GroupDetails, GroupId, User, UserAndGroups, UserId},
};
@@ -72,6 +73,7 @@ impl ValidationResults {
pub trait UserReadableBackendHandler {
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>>;
async fn get_schema(&self) -> Result<Schema>;
}
#[async_trait]
@@ -106,10 +108,13 @@ pub trait AdminBackendHandler:
#[async_trait]
impl<Handler: BackendHandler> UserReadableBackendHandler for Handler {
async fn get_user_details(&self, user_id: &UserId) -> Result<User> {
self.get_user_details(user_id).await
<Handler as UserBackendHandler>::get_user_details(self, user_id).await
}
async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>> {
self.get_user_groups(user_id).await
<Handler as UserBackendHandler>::get_user_groups(self, user_id).await
}
async fn get_schema(&self) -> Result<Schema> {
<Handler as SchemaBackendHandler>::get_schema(self).await
}
}
@@ -120,44 +125,44 @@ impl<Handler: BackendHandler> ReadonlyBackendHandler for Handler {
filters: Option<UserRequestFilter>,
get_groups: bool,
) -> Result<Vec<UserAndGroups>> {
self.list_users(filters, get_groups).await
<Handler as UserListerBackendHandler>::list_users(self, filters, get_groups).await
}
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>> {
self.list_groups(filters).await
<Handler as GroupListerBackendHandler>::list_groups(self, filters).await
}
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails> {
self.get_group_details(group_id).await
<Handler as GroupBackendHandler>::get_group_details(self, group_id).await
}
}
#[async_trait]
impl<Handler: BackendHandler> UserWriteableBackendHandler for Handler {
async fn update_user(&self, request: UpdateUserRequest) -> Result<()> {
self.update_user(request).await
<Handler as UserBackendHandler>::update_user(self, request).await
}
}
#[async_trait]
impl<Handler: BackendHandler> AdminBackendHandler for Handler {
async fn create_user(&self, request: CreateUserRequest) -> Result<()> {
self.create_user(request).await
<Handler as UserBackendHandler>::create_user(self, request).await
}
async fn delete_user(&self, user_id: &UserId) -> Result<()> {
self.delete_user(user_id).await
<Handler as UserBackendHandler>::delete_user(self, user_id).await
}
async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()> {
self.add_user_to_group(user_id, group_id).await
<Handler as UserBackendHandler>::add_user_to_group(self, user_id, group_id).await
}
async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()> {
self.remove_user_from_group(user_id, group_id).await
<Handler as UserBackendHandler>::remove_user_from_group(self, user_id, group_id).await
}
async fn update_group(&self, request: UpdateGroupRequest) -> Result<()> {
self.update_group(request).await
<Handler as GroupBackendHandler>::update_group(self, request).await
}
async fn create_group(&self, group_name: &str) -> Result<GroupId> {
self.create_group(group_name).await
<Handler as GroupBackendHandler>::create_group(self, group_name).await
}
async fn delete_group(&self, group_id: GroupId) -> Result<()> {
self.delete_group(group_id).await
<Handler as GroupBackendHandler>::delete_group(self, group_id).await
}
}
@@ -262,6 +267,15 @@ pub struct UserRestrictedListerBackendHandler<'a, Handler> {
pub user_filter: Option<UserId>,
}
#[async_trait]
impl<'a, Handler: SchemaBackendHandler + Sync> SchemaBackendHandler
for UserRestrictedListerBackendHandler<'a, Handler>
{
async fn get_schema(&self) -> Result<Schema> {
self.handler.get_schema().await
}
}
#[async_trait]
impl<'a, Handler: UserListerBackendHandler + Sync> UserListerBackendHandler
for UserRestrictedListerBackendHandler<'a, Handler>

View File

@@ -2,7 +2,7 @@ use crate::{
domain::{
handler::BackendHandler,
ldap::utils::{map_user_field, UserFieldType},
types::{GroupDetails, GroupId, UserColumn, UserId},
types::{GroupDetails, GroupId, JpegPhoto, UserColumn, UserId},
},
infra::{
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler},
@@ -236,15 +236,29 @@ impl<Handler: BackendHandler> User<Handler> {
}
fn first_name(&self) -> &str {
self.user.first_name.as_deref().unwrap_or("")
self.user
.attributes
.iter()
.find(|a| a.name == "first_name")
.map(|a| a.value.unwrap())
.unwrap_or("")
}
fn last_name(&self) -> &str {
self.user.last_name.as_deref().unwrap_or("")
self.user
.attributes
.iter()
.find(|a| a.name == "last_name")
.map(|a| a.value.unwrap())
.unwrap_or("")
}
fn avatar(&self) -> Option<String> {
self.user.avatar.as_ref().map(String::from)
self.user
.attributes
.iter()
.find(|a| a.name == "avatar")
.map(|a| String::from(&a.value.unwrap::<JpegPhoto>()))
}
fn creation_date(&self) -> chrono::DateTime<chrono::Utc> {

View File

@@ -1,6 +1,8 @@
use crate::{
domain::{
handler::{BackendHandler, BindRequest, CreateUserRequest, LoginHandler},
handler::{
BackendHandler, BindRequest, CreateUserRequest, LoginHandler, SchemaBackendHandler,
},
ldap::{
error::{LdapError, LdapResult},
group::{convert_groups_to_ldap_op, get_groups_list},
@@ -467,12 +469,17 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
.get_user_restricted_lister_handler(user_info);
let (users, groups) = self.do_search_internal(&backend_handler, request).await?;
let schema = backend_handler.get_schema().await.map_err(|e| LdapError {
code: LdapResultCode::OperationsError,
message: format!("Unable to get schema: {:#}", e),
})?;
let mut results = Vec::new();
if let Some(users) = users {
results.extend(convert_users_to_ldap_op(
users,
&request.attrs,
&self.ldap_info,
&schema,
));
}
if let Some(groups) = groups {
@@ -769,6 +776,7 @@ mod tests {
});
Ok(set)
});
setup_default_schema(&mut mock);
let mut ldap_handler = LdapHandler::new_for_tests(mock, "dc=Example,dc=com");
let request = LdapBindRequest {
dn: "uid=test,ou=people,dc=example,dc=coM".to_string(),
@@ -799,6 +807,44 @@ mod tests {
setup_bound_handler_with_group(mock, "lldap_admin").await
}
fn setup_default_schema(mock: &mut MockTestBackendHandler) {
mock.expect_get_schema().returning(|| {
Ok(Schema {
user_attributes: AttributeList {
attributes: vec![
AttributeSchema {
name: "avatar".to_owned(),
attribute_type: AttributeType::JpegPhoto,
is_list: false,
is_visible: true,
is_editable: true,
is_hardcoded: true,
},
AttributeSchema {
name: "first_name".to_owned(),
attribute_type: AttributeType::String,
is_list: false,
is_visible: true,
is_editable: true,
is_hardcoded: true,
},
AttributeSchema {
name: "last_name".to_owned(),
attribute_type: AttributeType::String,
is_list: false,
is_visible: true,
is_editable: true,
is_hardcoded: true,
},
],
},
group_attributes: AttributeList {
attributes: Vec::new(),
},
})
});
}
#[tokio::test]
async fn test_bind() {
let mut mock = MockTestBackendHandler::new();
@@ -1083,9 +1129,17 @@ mod tests {
user_id: UserId::new("bob_1"),
email: "bob@bobmail.bob".to_string(),
display_name: Some("Bôb Böbberson".to_string()),
first_name: Some("Bôb".to_string()),
last_name: Some("Böbberson".to_string()),
uuid: uuid!("698e1d5f-7a40-3151-8745-b9b8a37839da"),
attributes: vec![
AttributeValue {
name: "first_name".to_owned(),
value: Serialized::from("Bôb"),
},
AttributeValue {
name: "last_name".to_owned(),
value: Serialized::from("Böbberson"),
},
],
..Default::default()
},
groups: None,
@@ -1095,9 +1149,20 @@ mod tests {
user_id: UserId::new("jim"),
email: "jim@cricket.jim".to_string(),
display_name: Some("Jimminy Cricket".to_string()),
first_name: Some("Jim".to_string()),
last_name: Some("Cricket".to_string()),
avatar: Some(JpegPhoto::for_tests()),
attributes: vec![
AttributeValue {
name: "avatar".to_owned(),
value: Serialized::from(&JpegPhoto::for_tests()),
},
AttributeValue {
name: "first_name".to_owned(),
value: Serialized::from("Jim"),
},
AttributeValue {
name: "last_name".to_owned(),
value: Serialized::from("Cricket"),
},
],
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
creation_date: Utc
.with_ymd_and_hms(2014, 7, 8, 9, 10, 11)
@@ -1746,8 +1811,16 @@ mod tests {
user_id: UserId::new("bob_1"),
email: "bob@bobmail.bob".to_string(),
display_name: Some("Bôb Böbberson".to_string()),
first_name: Some("Bôb".to_string()),
last_name: Some("Böbberson".to_string()),
attributes: vec![
AttributeValue {
name: "first_name".to_owned(),
value: Serialized::from("Bôb"),
},
AttributeValue {
name: "last_name".to_owned(),
value: Serialized::from("Böbberson"),
},
],
..Default::default()
},
groups: None,
@@ -1820,8 +1893,16 @@ mod tests {
user_id: UserId::new("bob_1"),
email: "bob@bobmail.bob".to_string(),
display_name: Some("Bôb Böbberson".to_string()),
last_name: Some("Böbberson".to_string()),
avatar: Some(JpegPhoto::for_tests()),
attributes: vec![
AttributeValue {
name: "avatar".to_owned(),
value: Serialized::from(&JpegPhoto::for_tests()),
},
AttributeValue {
name: "last_name".to_owned(),
value: Serialized::from("Böbberson"),
},
],
uuid: uuid!("b4ac75e0-2900-3e21-926c-2f732c26b3fc"),
..Default::default()
},