mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
ldap: add support for memberOf attribute
The "memberOf" filter was already supported, but not the attribute. Fixes #179
This commit is contained in:
parent
1f632a8069
commit
da186fab38
@ -34,7 +34,7 @@ impl From<String> for UserId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
|
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
@ -134,9 +134,19 @@ pub struct GroupId(pub i32);
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::FromRow)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::FromRow)]
|
||||||
pub struct GroupIdAndName(pub GroupId, pub String);
|
pub struct GroupIdAndName(pub GroupId, pub String);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct UserAndGroups {
|
||||||
|
pub user: User,
|
||||||
|
pub groups: Option<Vec<GroupIdAndName>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait BackendHandler: Clone + Send {
|
pub trait BackendHandler: Clone + Send {
|
||||||
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>;
|
async fn list_users(
|
||||||
|
&self,
|
||||||
|
filters: Option<UserRequestFilter>,
|
||||||
|
get_groups: bool,
|
||||||
|
) -> Result<Vec<UserAndGroups>>;
|
||||||
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
||||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
||||||
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
||||||
@ -159,7 +169,7 @@ mockall::mock! {
|
|||||||
}
|
}
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BackendHandler for TestBackendHandler {
|
impl BackendHandler for TestBackendHandler {
|
||||||
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>;
|
async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
|
||||||
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
||||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
||||||
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
||||||
|
@ -3,7 +3,7 @@ use crate::infra::configuration::Configuration;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use sea_query::{Expr, Iden, Order, Query, SimpleExpr};
|
use sea_query::{Expr, Iden, Order, Query, SimpleExpr};
|
||||||
use sqlx::Row;
|
use sqlx::{FromRow, Row};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -109,7 +109,11 @@ fn get_group_filter_expr(filter: GroupRequestFilter) -> SimpleExpr {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BackendHandler for SqlBackendHandler {
|
impl BackendHandler for SqlBackendHandler {
|
||||||
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>> {
|
async fn list_users(
|
||||||
|
&self,
|
||||||
|
filters: Option<UserRequestFilter>,
|
||||||
|
get_groups: bool,
|
||||||
|
) -> Result<Vec<UserAndGroups>> {
|
||||||
let query = {
|
let query = {
|
||||||
let mut query_builder = Query::select()
|
let mut query_builder = Query::select()
|
||||||
.column((Users::Table, Users::UserId))
|
.column((Users::Table, Users::UserId))
|
||||||
@ -122,6 +126,26 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
.from(Users::Table)
|
.from(Users::Table)
|
||||||
.order_by((Users::Table, Users::UserId), Order::Asc)
|
.order_by((Users::Table, Users::UserId), Order::Asc)
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
let add_join_group_tables = |builder: &mut sea_query::SelectStatement| {
|
||||||
|
builder
|
||||||
|
.left_join(
|
||||||
|
Memberships::Table,
|
||||||
|
Expr::tbl(Users::Table, Users::UserId)
|
||||||
|
.equals(Memberships::Table, Memberships::UserId),
|
||||||
|
)
|
||||||
|
.left_join(
|
||||||
|
Groups::Table,
|
||||||
|
Expr::tbl(Memberships::Table, Memberships::GroupId)
|
||||||
|
.equals(Groups::Table, Groups::GroupId),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if get_groups {
|
||||||
|
add_join_group_tables(&mut query_builder);
|
||||||
|
query_builder
|
||||||
|
.column((Groups::Table, Groups::GroupId))
|
||||||
|
.column((Groups::Table, Groups::DisplayName))
|
||||||
|
.order_by((Groups::Table, Groups::DisplayName), Order::Asc);
|
||||||
|
}
|
||||||
if let Some(filter) = filters {
|
if let Some(filter) = filters {
|
||||||
if filter == UserRequestFilter::Not(Box::new(UserRequestFilter::And(Vec::new()))) {
|
if filter == UserRequestFilter::Not(Box::new(UserRequestFilter::And(Vec::new()))) {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
@ -131,31 +155,48 @@ impl BackendHandler for SqlBackendHandler {
|
|||||||
{
|
{
|
||||||
let (RequiresGroup(requires_group), condition) = get_user_filter_expr(filter);
|
let (RequiresGroup(requires_group), condition) = get_user_filter_expr(filter);
|
||||||
query_builder.and_where(condition);
|
query_builder.and_where(condition);
|
||||||
if requires_group {
|
if requires_group && !get_groups {
|
||||||
query_builder
|
add_join_group_tables(&mut query_builder);
|
||||||
.left_join(
|
|
||||||
Memberships::Table,
|
|
||||||
Expr::tbl(Users::Table, Users::UserId)
|
|
||||||
.equals(Memberships::Table, Memberships::UserId),
|
|
||||||
)
|
|
||||||
.left_join(
|
|
||||||
Groups::Table,
|
|
||||||
Expr::tbl(Memberships::Table, Memberships::GroupId)
|
|
||||||
.equals(Groups::Table, Groups::GroupId),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query_builder.to_string(DbQueryBuilder {})
|
query_builder.to_string(DbQueryBuilder {})
|
||||||
};
|
};
|
||||||
|
log::error!("query: {}", &query);
|
||||||
|
|
||||||
let results = sqlx::query_as::<_, User>(&query)
|
// For group_by.
|
||||||
.fetch(&self.sql_pool)
|
use itertools::Itertools;
|
||||||
.collect::<Vec<sqlx::Result<User>>>()
|
let mut users = Vec::new();
|
||||||
.await;
|
// The rows are returned sorted by user_id. We group them by
|
||||||
|
// this key which gives us one element (`rows`) per group.
|
||||||
|
for (_, rows) in &sqlx::query(&query)
|
||||||
|
.fetch_all(&self.sql_pool)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.group_by(|row| row.get::<UserId, _>(&*Users::UserId.to_string()))
|
||||||
|
{
|
||||||
|
let mut rows = rows.peekable();
|
||||||
|
users.push(UserAndGroups {
|
||||||
|
user: User::from_row(rows.peek().unwrap()).unwrap(),
|
||||||
|
groups: if get_groups {
|
||||||
|
Some(
|
||||||
|
rows.map(|row| {
|
||||||
|
GroupIdAndName(
|
||||||
|
GroupId(row.get::<i32, _>(&*Groups::GroupId.to_string())),
|
||||||
|
row.get::<String, _>(&*Groups::DisplayName.to_string()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter(|g| !g.1.is_empty())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(results.into_iter().collect::<sqlx::Result<Vec<User>>>()?)
|
Ok(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>> {
|
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>> {
|
||||||
@ -486,6 +527,19 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_user_names(
|
||||||
|
handler: &SqlBackendHandler,
|
||||||
|
filters: Option<UserRequestFilter>,
|
||||||
|
) -> Vec<String> {
|
||||||
|
handler
|
||||||
|
.list_users(filters, false)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|u| u.user.user_id.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_bind_admin() {
|
async fn test_bind_admin() {
|
||||||
let sql_pool = get_in_memory_db().await;
|
let sql_pool = get_in_memory_db().await;
|
||||||
@ -558,50 +612,70 @@ mod tests {
|
|||||||
insert_user(&handler, "bob", "bob00").await;
|
insert_user(&handler, "bob", "bob00").await;
|
||||||
insert_user(&handler, "patrick", "pass").await;
|
insert_user(&handler, "patrick", "pass").await;
|
||||||
insert_user(&handler, "John", "Pa33w0rd!").await;
|
insert_user(&handler, "John", "Pa33w0rd!").await;
|
||||||
|
let group_1 = insert_group(&handler, "Best Group").await;
|
||||||
|
let group_2 = insert_group(&handler, "Worst Group").await;
|
||||||
|
insert_membership(&handler, group_1, "bob").await;
|
||||||
|
insert_membership(&handler, group_1, "patrick").await;
|
||||||
|
insert_membership(&handler, group_2, "patrick").await;
|
||||||
|
insert_membership(&handler, group_2, "John").await;
|
||||||
{
|
{
|
||||||
let users = handler
|
let users = get_user_names(&handler, None).await;
|
||||||
.list_users(None)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|u| u.user_id.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(users, vec!["bob", "john", "patrick"]);
|
assert_eq!(users, vec!["bob", "john", "patrick"]);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let users = handler
|
let users = get_user_names(
|
||||||
.list_users(Some(UserRequestFilter::UserId(UserId::new("bob"))))
|
&handler,
|
||||||
.await
|
Some(UserRequestFilter::UserId(UserId::new("bob"))),
|
||||||
.unwrap()
|
)
|
||||||
.into_iter()
|
.await;
|
||||||
.map(|u| u.user_id.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(users, vec!["bob"]);
|
assert_eq!(users, vec!["bob"]);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let users = handler
|
let users = get_user_names(
|
||||||
.list_users(Some(UserRequestFilter::Or(vec![
|
&handler,
|
||||||
|
Some(UserRequestFilter::Or(vec![
|
||||||
UserRequestFilter::UserId(UserId::new("bob")),
|
UserRequestFilter::UserId(UserId::new("bob")),
|
||||||
UserRequestFilter::UserId(UserId::new("John")),
|
UserRequestFilter::UserId(UserId::new("John")),
|
||||||
])))
|
])),
|
||||||
.await
|
)
|
||||||
.unwrap()
|
.await;
|
||||||
.into_iter()
|
|
||||||
.map(|u| u.user_id.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(users, vec!["bob", "john"]);
|
assert_eq!(users, vec!["bob", "john"]);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let users = get_user_names(
|
||||||
|
&handler,
|
||||||
|
Some(UserRequestFilter::Not(Box::new(UserRequestFilter::UserId(
|
||||||
|
UserId::new("bob"),
|
||||||
|
)))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(users, vec!["john", "patrick"]);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
let users = handler
|
let users = handler
|
||||||
.list_users(Some(UserRequestFilter::Not(Box::new(
|
.list_users(None, true)
|
||||||
UserRequestFilter::UserId(UserId::new("bob")),
|
|
||||||
))))
|
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|u| u.user_id.to_string())
|
.map(|u| {
|
||||||
|
(
|
||||||
|
u.user.user_id.to_string(),
|
||||||
|
u.groups
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|g| g.0)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(users, vec!["john", "patrick"]);
|
assert_eq!(
|
||||||
|
users,
|
||||||
|
vec![
|
||||||
|
("bob".to_string(), vec![group_1]),
|
||||||
|
("john".to_string(), vec![group_2]),
|
||||||
|
("patrick".to_string(), vec![group_1, group_2]),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -763,29 +837,13 @@ mod tests {
|
|||||||
// Remove a user
|
// Remove a user
|
||||||
let _request_result = handler.delete_user(&UserId::new("Jennz")).await.unwrap();
|
let _request_result = handler.delete_user(&UserId::new("Jennz")).await.unwrap();
|
||||||
|
|
||||||
let users = handler
|
assert_eq!(get_user_names(&handler, None).await, vec!["hector", "val"]);
|
||||||
.list_users(None)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|u| u.user_id.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert_eq!(users, vec!["hector", "val"]);
|
|
||||||
|
|
||||||
// Insert new user and remove two
|
// Insert new user and remove two
|
||||||
insert_user(&handler, "NewBoi", "Joni").await;
|
insert_user(&handler, "NewBoi", "Joni").await;
|
||||||
let _request_result = handler.delete_user(&UserId::new("Hector")).await.unwrap();
|
let _request_result = handler.delete_user(&UserId::new("Hector")).await.unwrap();
|
||||||
let _request_result = handler.delete_user(&UserId::new("NewBoi")).await.unwrap();
|
let _request_result = handler.delete_user(&UserId::new("NewBoi")).await.unwrap();
|
||||||
|
|
||||||
let users = handler
|
assert_eq!(get_user_names(&handler, None).await, vec!["val"]);
|
||||||
.list_users(None)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|u| u.user_id.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert_eq!(users, vec!["val"]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
type DomainRequestFilter = crate::domain::handler::UserRequestFilter;
|
type DomainRequestFilter = crate::domain::handler::UserRequestFilter;
|
||||||
type DomainUser = crate::domain::handler::User;
|
type DomainUser = crate::domain::handler::User;
|
||||||
type DomainGroup = crate::domain::handler::Group;
|
type DomainGroup = crate::domain::handler::Group;
|
||||||
|
type DomainUserAndGroups = crate::domain::handler::UserAndGroups;
|
||||||
use super::api::Context;
|
use super::api::Context;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
|
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
|
||||||
@ -126,7 +127,7 @@ impl<Handler: BackendHandler + Sync> Query<Handler> {
|
|||||||
}
|
}
|
||||||
Ok(context
|
Ok(context
|
||||||
.handler
|
.handler
|
||||||
.list_users(filters.map(TryInto::try_into).transpose()?)
|
.list_users(filters.map(TryInto::try_into).transpose()?, false)
|
||||||
.await
|
.await
|
||||||
.map(|v| v.into_iter().map(Into::into).collect())?)
|
.map(|v| v.into_iter().map(Into::into).collect())?)
|
||||||
}
|
}
|
||||||
@ -215,6 +216,15 @@ impl<Handler: BackendHandler> From<DomainUser> for User<Handler> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Handler: BackendHandler> From<DomainUserAndGroups> for User<Handler> {
|
||||||
|
fn from(user: DomainUserAndGroups) -> Self {
|
||||||
|
Self {
|
||||||
|
user: user.user,
|
||||||
|
_phantom: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
/// Represents a single group.
|
/// Represents a single group.
|
||||||
pub struct Group<Handler: BackendHandler> {
|
pub struct Group<Handler: BackendHandler> {
|
||||||
@ -239,9 +249,10 @@ impl<Handler: BackendHandler + Sync> Group<Handler> {
|
|||||||
}
|
}
|
||||||
Ok(context
|
Ok(context
|
||||||
.handler
|
.handler
|
||||||
.list_users(Some(DomainRequestFilter::MemberOfId(GroupId(
|
.list_users(
|
||||||
self.group_id,
|
Some(DomainRequestFilter::MemberOfId(GroupId(self.group_id))),
|
||||||
))))
|
false,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(|v| v.into_iter().map(Into::into).collect())?)
|
.map(|v| v.into_iter().map(Into::into).collect())?)
|
||||||
}
|
}
|
||||||
@ -365,21 +376,33 @@ mod tests {
|
|||||||
|
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users()
|
mock.expect_list_users()
|
||||||
.with(eq(Some(UserRequestFilter::Or(vec![
|
.with(
|
||||||
UserRequestFilter::Equality("id".to_string(), "bob".to_string()),
|
eq(Some(UserRequestFilter::Or(vec![
|
||||||
UserRequestFilter::Equality("email".to_string(), "robert@bobbers.on".to_string()),
|
UserRequestFilter::Equality("id".to_string(), "bob".to_string()),
|
||||||
]))))
|
UserRequestFilter::Equality(
|
||||||
.return_once(|_| {
|
"email".to_string(),
|
||||||
|
"robert@bobbers.on".to_string(),
|
||||||
|
),
|
||||||
|
]))),
|
||||||
|
eq(false),
|
||||||
|
)
|
||||||
|
.return_once(|_, _| {
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
DomainUser {
|
DomainUserAndGroups {
|
||||||
user_id: UserId::new("bob"),
|
user: DomainUser {
|
||||||
email: "bob@bobbers.on".to_string(),
|
user_id: UserId::new("bob"),
|
||||||
..Default::default()
|
email: "bob@bobbers.on".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
},
|
},
|
||||||
DomainUser {
|
DomainUserAndGroups {
|
||||||
user_id: UserId::new("robert"),
|
user: DomainUser {
|
||||||
email: "robert@bobbers.on".to_string(),
|
user_id: UserId::new("robert"),
|
||||||
..Default::default()
|
email: "robert@bobbers.on".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
domain::{
|
domain::{
|
||||||
handler::{
|
handler::{
|
||||||
BackendHandler, BindRequest, Group, GroupRequestFilter, LoginHandler, User, UserId,
|
BackendHandler, BindRequest, Group, GroupIdAndName, GroupRequestFilter, LoginHandler,
|
||||||
UserRequestFilter,
|
User, UserId, UserRequestFilter,
|
||||||
},
|
},
|
||||||
opaque_handler::OpaqueHandler,
|
opaque_handler::OpaqueHandler,
|
||||||
},
|
},
|
||||||
@ -109,6 +109,8 @@ fn get_user_attribute(
|
|||||||
user: &User,
|
user: &User,
|
||||||
attribute: &str,
|
attribute: &str,
|
||||||
dn: &str,
|
dn: &str,
|
||||||
|
base_dn_str: &str,
|
||||||
|
groups: Option<&[GroupIdAndName]>,
|
||||||
ignored_user_attributes: &[String],
|
ignored_user_attributes: &[String],
|
||||||
) -> Result<Option<Vec<String>>> {
|
) -> Result<Option<Vec<String>>> {
|
||||||
let attribute = attribute.to_ascii_lowercase();
|
let attribute = attribute.to_ascii_lowercase();
|
||||||
@ -124,6 +126,11 @@ fn get_user_attribute(
|
|||||||
"mail" => vec![user.email.clone()],
|
"mail" => vec![user.email.clone()],
|
||||||
"givenname" => vec![user.first_name.clone()],
|
"givenname" => vec![user.first_name.clone()],
|
||||||
"sn" => vec![user.last_name.clone()],
|
"sn" => vec![user.last_name.clone()],
|
||||||
|
"memberof" => groups
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|id_and_name| format!("uid={},ou=groups,{}", &id_and_name.1, base_dn_str))
|
||||||
|
.collect(),
|
||||||
"cn" | "displayname" => vec![user.display_name.clone()],
|
"cn" | "displayname" => vec![user.display_name.clone()],
|
||||||
"createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()],
|
"createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()],
|
||||||
"1.1" => return Ok(None),
|
"1.1" => return Ok(None),
|
||||||
@ -179,17 +186,24 @@ fn make_ldap_search_user_result_entry(
|
|||||||
user: User,
|
user: User,
|
||||||
base_dn_str: &str,
|
base_dn_str: &str,
|
||||||
attributes: &[String],
|
attributes: &[String],
|
||||||
|
groups: Option<&[GroupIdAndName]>,
|
||||||
ignored_user_attributes: &[String],
|
ignored_user_attributes: &[String],
|
||||||
) -> Result<LdapSearchResultEntry> {
|
) -> Result<LdapSearchResultEntry> {
|
||||||
let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
|
let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
|
||||||
|
|
||||||
let expanded_attributes = expand_attribute_wildcards(attributes, ALL_USER_ATTRIBUTE_KEYS);
|
|
||||||
Ok(LdapSearchResultEntry {
|
Ok(LdapSearchResultEntry {
|
||||||
dn: dn.clone(),
|
dn: dn.clone(),
|
||||||
attributes: expanded_attributes
|
attributes: attributes
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|a| {
|
.filter_map(|a| {
|
||||||
let values = match get_user_attribute(&user, a, &dn, ignored_user_attributes) {
|
let values = match get_user_attribute(
|
||||||
|
&user,
|
||||||
|
a,
|
||||||
|
&dn,
|
||||||
|
base_dn_str,
|
||||||
|
groups,
|
||||||
|
ignored_user_attributes,
|
||||||
|
) {
|
||||||
Err(e) => return Some(Err(e)),
|
Err(e) => return Some(Err(e)),
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
}?;
|
}?;
|
||||||
@ -625,7 +639,16 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
|
UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let users = match self.backend_handler.list_users(Some(filters)).await {
|
let expanded_attributes =
|
||||||
|
expand_attribute_wildcards(&request.attrs, ALL_USER_ATTRIBUTE_KEYS);
|
||||||
|
let need_groups = expanded_attributes
|
||||||
|
.iter()
|
||||||
|
.any(|s| s.to_ascii_lowercase() == "memberof");
|
||||||
|
let users = match self
|
||||||
|
.backend_handler
|
||||||
|
.list_users(Some(filters), need_groups)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(users) => users,
|
Ok(users) => users,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return vec![make_search_error(
|
return vec![make_search_error(
|
||||||
@ -639,9 +662,10 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|u| {
|
.map(|u| {
|
||||||
make_ldap_search_user_result_entry(
|
make_ldap_search_user_result_entry(
|
||||||
u,
|
u.user,
|
||||||
&self.base_dn_str,
|
&self.base_dn_str,
|
||||||
&request.attrs,
|
&expanded_attributes,
|
||||||
|
u.groups.as_deref(),
|
||||||
&self.ignored_user_attributes,
|
&self.ignored_user_attributes,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -903,7 +927,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BackendHandler for TestBackendHandler {
|
impl BackendHandler for TestBackendHandler {
|
||||||
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>;
|
async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
|
||||||
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
||||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
||||||
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
||||||
@ -1070,15 +1094,21 @@ mod tests {
|
|||||||
async fn test_search_regular_user() {
|
async fn test_search_regular_user() {
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users()
|
mock.expect_list_users()
|
||||||
.with(eq(Some(UserRequestFilter::And(vec![
|
.with(
|
||||||
UserRequestFilter::And(vec![]),
|
eq(Some(UserRequestFilter::And(vec![
|
||||||
UserRequestFilter::UserId(UserId::new("test")),
|
UserRequestFilter::And(vec![]),
|
||||||
]))))
|
UserRequestFilter::UserId(UserId::new("test")),
|
||||||
|
]))),
|
||||||
|
eq(false),
|
||||||
|
)
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| {
|
.return_once(|_, _| {
|
||||||
Ok(vec![User {
|
Ok(vec![UserAndGroups {
|
||||||
user_id: UserId::new("test"),
|
user: User {
|
||||||
..Default::default()
|
user_id: UserId::new("test"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
}])
|
}])
|
||||||
});
|
});
|
||||||
let mut ldap_handler = setup_bound_handler_with_group(mock, "regular").await;
|
let mut ldap_handler = setup_bound_handler_with_group(mock, "regular").await;
|
||||||
@ -1101,9 +1131,9 @@ mod tests {
|
|||||||
async fn test_search_readonly_user() {
|
async fn test_search_readonly_user() {
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users()
|
mock.expect_list_users()
|
||||||
.with(eq(Some(UserRequestFilter::And(vec![]))))
|
.with(eq(Some(UserRequestFilter::And(vec![]))), eq(false))
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| Ok(vec![]));
|
.return_once(|_, _| Ok(vec![]));
|
||||||
let mut ldap_handler = setup_bound_readonly_handler(mock).await;
|
let mut ldap_handler = setup_bound_readonly_handler(mock).await;
|
||||||
|
|
||||||
let request =
|
let request =
|
||||||
@ -1114,6 +1144,42 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_member_of() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
mock.expect_list_users()
|
||||||
|
.with(eq(Some(UserRequestFilter::And(vec![]))), eq(true))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_, _| {
|
||||||
|
Ok(vec![UserAndGroups {
|
||||||
|
user: User {
|
||||||
|
user_id: UserId::new("bob"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: Some(vec![GroupIdAndName(GroupId(42), "rockstars".to_string())]),
|
||||||
|
}])
|
||||||
|
});
|
||||||
|
let mut ldap_handler = setup_bound_readonly_handler(mock).await;
|
||||||
|
|
||||||
|
let request = make_user_search_request::<String>(
|
||||||
|
LdapFilter::And(vec![]),
|
||||||
|
vec!["memberOf".to_string()],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.do_search(&request).await,
|
||||||
|
vec![
|
||||||
|
LdapOp::SearchResultEntry(LdapSearchResultEntry {
|
||||||
|
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
|
||||||
|
attributes: vec![LdapPartialAttribute {
|
||||||
|
atype: "memberOf".to_string(),
|
||||||
|
vals: vec!["uid=rockstars,ou=groups,dc=example,dc=com".to_string()]
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
make_search_success(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_bind_invalid_dn() {
|
async fn test_bind_invalid_dn() {
|
||||||
let mock = MockTestBackendHandler::new();
|
let mock = MockTestBackendHandler::new();
|
||||||
@ -1199,23 +1265,29 @@ mod tests {
|
|||||||
async fn test_search_users() {
|
async fn test_search_users() {
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users().times(1).return_once(|_| {
|
mock.expect_list_users().times(1).return_once(|_, _| {
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
User {
|
UserAndGroups {
|
||||||
user_id: UserId::new("bob_1"),
|
user: User {
|
||||||
email: "bob@bobmail.bob".to_string(),
|
user_id: UserId::new("bob_1"),
|
||||||
display_name: "Bôb Böbberson".to_string(),
|
email: "bob@bobmail.bob".to_string(),
|
||||||
first_name: "Bôb".to_string(),
|
display_name: "Bôb Böbberson".to_string(),
|
||||||
last_name: "Böbberson".to_string(),
|
first_name: "Bôb".to_string(),
|
||||||
..Default::default()
|
last_name: "Böbberson".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
},
|
},
|
||||||
User {
|
UserAndGroups {
|
||||||
user_id: UserId::new("jim"),
|
user: User {
|
||||||
email: "jim@cricket.jim".to_string(),
|
user_id: UserId::new("jim"),
|
||||||
display_name: "Jimminy Cricket".to_string(),
|
email: "jim@cricket.jim".to_string(),
|
||||||
first_name: "Jim".to_string(),
|
display_name: "Jimminy Cricket".to_string(),
|
||||||
last_name: "Cricket".to_string(),
|
first_name: "Jim".to_string(),
|
||||||
creation_date: Utc.ymd(2014, 7, 8).and_hms(9, 10, 11),
|
last_name: "Cricket".to_string(),
|
||||||
|
creation_date: Utc.ymd(2014, 7, 8).and_hms(9, 10, 11),
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
@ -1559,19 +1631,24 @@ mod tests {
|
|||||||
async fn test_search_filters() {
|
async fn test_search_filters() {
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users()
|
mock.expect_list_users()
|
||||||
.with(eq(Some(UserRequestFilter::And(vec![
|
.with(
|
||||||
UserRequestFilter::Or(vec![
|
eq(Some(UserRequestFilter::And(vec![UserRequestFilter::Or(
|
||||||
UserRequestFilter::Not(Box::new(UserRequestFilter::UserId(UserId::new("bob")))),
|
vec![
|
||||||
UserRequestFilter::And(vec![]),
|
UserRequestFilter::Not(Box::new(UserRequestFilter::UserId(UserId::new(
|
||||||
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
|
"bob",
|
||||||
UserRequestFilter::And(vec![]),
|
)))),
|
||||||
UserRequestFilter::And(vec![]),
|
UserRequestFilter::And(vec![]),
|
||||||
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
|
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
|
||||||
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
|
UserRequestFilter::And(vec![]),
|
||||||
]),
|
UserRequestFilter::And(vec![]),
|
||||||
]))))
|
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
|
||||||
|
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
|
||||||
|
],
|
||||||
|
)]))),
|
||||||
|
eq(false),
|
||||||
|
)
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| Ok(vec![]));
|
.return_once(|_, _| Ok(vec![]));
|
||||||
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
||||||
let request = make_user_search_request(
|
let request = make_user_search_request(
|
||||||
LdapFilter::And(vec![LdapFilter::Or(vec![
|
LdapFilter::And(vec![LdapFilter::Or(vec![
|
||||||
@ -1595,12 +1672,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_search_member_of() {
|
async fn test_search_member_of_filter() {
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users()
|
mock.expect_list_users()
|
||||||
.with(eq(Some(UserRequestFilter::MemberOf("group_1".to_string()))))
|
.with(
|
||||||
|
eq(Some(UserRequestFilter::MemberOf("group_1".to_string()))),
|
||||||
|
eq(false),
|
||||||
|
)
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| Ok(vec![]));
|
.return_once(|_, _| Ok(vec![]));
|
||||||
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
||||||
let request = make_user_search_request(
|
let request = make_user_search_request(
|
||||||
LdapFilter::Equality(
|
LdapFilter::Equality(
|
||||||
@ -1644,16 +1724,22 @@ mod tests {
|
|||||||
async fn test_search_filters_lowercase() {
|
async fn test_search_filters_lowercase() {
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users()
|
mock.expect_list_users()
|
||||||
.with(eq(Some(UserRequestFilter::And(vec![
|
.with(
|
||||||
UserRequestFilter::Or(vec![UserRequestFilter::Not(Box::new(
|
eq(Some(UserRequestFilter::And(vec![UserRequestFilter::Or(
|
||||||
UserRequestFilter::Equality("first_name".to_string(), "bob".to_string()),
|
vec![UserRequestFilter::Not(Box::new(
|
||||||
))]),
|
UserRequestFilter::Equality("first_name".to_string(), "bob".to_string()),
|
||||||
]))))
|
))],
|
||||||
|
)]))),
|
||||||
|
eq(false),
|
||||||
|
)
|
||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|_| {
|
.return_once(|_, _| {
|
||||||
Ok(vec![User {
|
Ok(vec![UserAndGroups {
|
||||||
user_id: UserId::new("bob_1"),
|
user: User {
|
||||||
..Default::default()
|
user_id: UserId::new("bob_1"),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
}])
|
}])
|
||||||
});
|
});
|
||||||
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
||||||
@ -1686,14 +1772,17 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_search_both() {
|
async fn test_search_both() {
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
mock.expect_list_users().times(1).return_once(|_| {
|
mock.expect_list_users().times(1).return_once(|_, _| {
|
||||||
Ok(vec![User {
|
Ok(vec![UserAndGroups {
|
||||||
user_id: UserId::new("bob_1"),
|
user: User {
|
||||||
email: "bob@bobmail.bob".to_string(),
|
user_id: UserId::new("bob_1"),
|
||||||
display_name: "Bôb Böbberson".to_string(),
|
email: "bob@bobmail.bob".to_string(),
|
||||||
first_name: "Bôb".to_string(),
|
display_name: "Bôb Böbberson".to_string(),
|
||||||
last_name: "Böbberson".to_string(),
|
first_name: "Bôb".to_string(),
|
||||||
..Default::default()
|
last_name: "Böbberson".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
}])
|
}])
|
||||||
});
|
});
|
||||||
mock.expect_list_groups()
|
mock.expect_list_groups()
|
||||||
@ -1763,14 +1852,17 @@ mod tests {
|
|||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
|
||||||
mock.expect_list_users().returning(|_| {
|
mock.expect_list_users().returning(|_, _| {
|
||||||
Ok(vec![User {
|
Ok(vec![UserAndGroups {
|
||||||
user_id: UserId::new("bob_1"),
|
user: User {
|
||||||
email: "bob@bobmail.bob".to_string(),
|
user_id: UserId::new("bob_1"),
|
||||||
display_name: "Bôb Böbberson".to_string(),
|
email: "bob@bobmail.bob".to_string(),
|
||||||
first_name: "Bôb".to_string(),
|
display_name: "Bôb Böbberson".to_string(),
|
||||||
last_name: "Böbberson".to_string(),
|
first_name: "Bôb".to_string(),
|
||||||
..Default::default()
|
last_name: "Böbberson".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
groups: None,
|
||||||
}])
|
}])
|
||||||
});
|
});
|
||||||
mock.expect_list_groups()
|
mock.expect_list_groups()
|
||||||
|
@ -35,7 +35,7 @@ mockall::mock! {
|
|||||||
}
|
}
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BackendHandler for TestTcpBackendHandler {
|
impl BackendHandler for TestTcpBackendHandler {
|
||||||
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>;
|
async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
|
||||||
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
||||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
||||||
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
|
||||||
|
Loading…
Reference in New Issue
Block a user