server: add tests for ldap modify

This commit is contained in:
Valentin Tolmer
2025-04-04 20:21:35 -05:00
committed by nitnelave
parent 0a05a091d8
commit c3ae149ae3
4 changed files with 239 additions and 31 deletions
+5 -2
View File
@@ -56,8 +56,11 @@ mod tests {
ldap::handler::tests::setup_bound_admin_handler, test_utils::MockTestBackendHandler,
};
use chrono::TimeZone;
use lldap_domain::{types::*, uuid};
use lldap_domain_handlers::handler::*;
use lldap_domain::{
types::{Group, GroupId, User, UserAndGroups, UserId},
uuid,
};
use lldap_domain_handlers::handler::{GroupRequestFilter, UserRequestFilter};
use pretty_assertions::assert_eq;
use tokio;
+6 -4
View File
@@ -24,7 +24,7 @@ use ldap3_proto::proto::{
LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, OID_PASSWORD_MODIFY, OID_WHOAMI,
};
use lldap_auth::access_control::ValidationResults;
use lldap_domain::types::{AttributeName, UserId};
use lldap_domain::types::AttributeName;
use lldap_domain_handlers::handler::{BackendHandler, LoginHandler};
use tracing::{debug, instrument};
@@ -235,7 +235,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
}
#[instrument(skip_all, level = "debug", fields(dn = %request.dn))]
async fn do_modify_request(&mut self, request: &LdapModifyRequest) -> Vec<LdapOp> {
pub async fn do_modify_request(&mut self, request: &LdapModifyRequest) -> Vec<LdapOp> {
let credentials = match self.get_credentials() {
Credentials::Bound(cred) => cred,
Credentials::Unbound(err) => return err,
@@ -245,7 +245,6 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|credentials, user_id| {
self.backend_handler
.get_readable_handler(credentials, &user_id)
.expect("Unexpected permission error")
},
&self.ldap_info,
credentials,
@@ -328,7 +327,10 @@ pub mod tests {
};
use chrono::TimeZone;
use ldap3_proto::proto::{LdapBindCred, LdapWhoamiRequest};
use lldap_domain::{types::*, uuid};
use lldap_domain::{
types::{GroupDetails, GroupId, UserId},
uuid,
};
use lldap_domain_handlers::handler::*;
use mockall::predicate::eq;
use pretty_assertions::assert_eq;
+200 -1
View File
@@ -69,7 +69,10 @@ async fn handle_modify_change(
pub(crate) async fn handle_modify_request<'cred, UserBackendHandler>(
opaque_handler: &impl OpaqueHandler,
get_readable_handler: impl FnOnce(&'cred ValidationResults, UserId) -> &'cred UserBackendHandler,
get_readable_handler: impl FnOnce(
&'cred ValidationResults,
UserId,
) -> Option<&'cred UserBackendHandler>,
ldap_info: &LdapInfo,
credentials: &'cred ValidationResults,
request: &LdapModifyRequest,
@@ -85,6 +88,14 @@ where
) {
Ok(uid) => {
let user_is_admin = get_readable_handler(credentials, uid.clone())
.ok_or_else(|| LdapError {
code: LdapResultCode::InsufficentAccessRights,
message: format!(
"User `{}` cannot modify user `{}`",
credentials.user.as_str(),
uid.as_str()
),
})?
.get_user_groups(&uid)
.await
.map_err(|e| LdapError {
@@ -114,3 +125,191 @@ where
}),
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
use crate::infra::{
ldap::{
handler::tests::{
setup_bound_admin_handler, setup_bound_handler_with_group,
setup_bound_password_manager_handler,
},
password::tests::expect_password_change,
},
test_utils::MockTestBackendHandler,
};
use chrono::TimeZone;
use ldap3_proto::proto::LdapResult as LdapResultOp;
use lldap_domain::{
types::{GroupDetails, GroupId, GroupName, UserId},
uuid,
};
use mockall::predicate::eq;
use pretty_assertions::assert_eq;
use tokio;
fn setup_target_user_groups(
mock: &mut MockTestBackendHandler,
target_user: &str,
groups: Vec<&'static str>,
) {
mock.expect_get_user_groups()
.times(1)
.with(eq(UserId::from(target_user)))
.return_once(move |_| {
let mut g = HashSet::<GroupDetails>::new();
for group in groups {
g.insert(GroupDetails {
group_id: GroupId(42),
display_name: GroupName::from(group),
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
attributes: Vec::new(),
});
}
Ok(g)
});
}
fn make_password_modify_request(target_user: &str) -> LdapModifyRequest {
LdapModifyRequest {
dn: format!("uid={},ou=people,dc=example,dc=com", target_user),
changes: vec![LdapModify {
operation: LdapModifyType::Replace,
modification: ldap3_proto::LdapPartialAttribute {
atype: "userPassword".to_string(),
vals: vec![b"tommy".to_vec()],
},
}],
}
}
fn make_modify_success_response() -> Vec<LdapOp> {
vec![LdapOp::ModifyResponse(LdapResultOp {
code: LdapResultCode::Success,
matcheddn: "".to_string(),
message: "".to_string(),
referral: vec![],
})]
}
fn make_modify_failure_response(code: LdapResultCode, message: &str) -> Vec<LdapOp> {
vec![LdapOp::ModifyResponse(LdapResultOp {
code,
matcheddn: "".to_string(),
message: message.to_string(),
referral: vec![],
})]
}
#[tokio::test]
async fn test_modify_password_of_regular_as_admin() {
let mut mock = MockTestBackendHandler::new();
setup_target_user_groups(&mut mock, "bob", Vec::new());
expect_password_change(&mut mock, "bob");
let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_password_modify_request("bob");
assert_eq!(
ldap_handler.do_modify_request(&request).await,
make_modify_success_response()
);
}
#[tokio::test]
async fn test_modify_password_of_regular_as_regular() {
let mut mock = MockTestBackendHandler::new();
setup_target_user_groups(&mut mock, "test", Vec::new());
expect_password_change(&mut mock, "test");
let mut ldap_handler = setup_bound_handler_with_group(mock, "regular").await;
let request = make_password_modify_request("test");
assert_eq!(
ldap_handler.do_modify_request(&request).await,
make_modify_success_response()
);
}
#[tokio::test]
async fn test_modify_password_of_regular_as_password_manager() {
let mut mock = MockTestBackendHandler::new();
setup_target_user_groups(&mut mock, "bob", Vec::new());
expect_password_change(&mut mock, "bob");
let mut ldap_handler = setup_bound_password_manager_handler(mock).await;
let request = make_password_modify_request("bob");
assert_eq!(
ldap_handler.do_modify_request(&request).await,
make_modify_success_response()
);
}
#[tokio::test]
async fn test_modify_password_of_admin_as_password_manager() {
let mut mock = MockTestBackendHandler::new();
setup_target_user_groups(&mut mock, "bob", vec!["lldap_admin"]);
let mut ldap_handler = setup_bound_password_manager_handler(mock).await;
let request = make_password_modify_request("bob");
assert_eq!(
ldap_handler.do_modify_request(&request).await,
make_modify_failure_response(
LdapResultCode::InsufficentAccessRights,
"User `test` cannot modify the password of user `bob`"
)
);
}
#[tokio::test]
async fn test_modify_password_of_other_regular_as_regular() {
let mut ldap_handler =
setup_bound_handler_with_group(MockTestBackendHandler::new(), "regular").await;
let request = make_password_modify_request("bob");
assert_eq!(
ldap_handler.do_modify_request(&request).await,
make_modify_failure_response(
LdapResultCode::InsufficentAccessRights,
"User `test` cannot modify user `bob`"
)
);
}
#[tokio::test]
async fn test_modify_password_of_admin_as_admin() {
let mut mock = MockTestBackendHandler::new();
setup_target_user_groups(&mut mock, "test", vec!["lldap_admin"]);
expect_password_change(&mut mock, "test");
let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_password_modify_request("test");
assert_eq!(
ldap_handler.do_modify_request(&request).await,
make_modify_success_response()
);
}
#[tokio::test]
async fn test_modify_password_invalid_number_of_values() {
let mut mock = MockTestBackendHandler::new();
setup_target_user_groups(&mut mock, "bob", Vec::new());
let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = {
let target_user = "bob";
LdapModifyRequest {
dn: format!("uid={},ou=people,dc=example,dc=com", target_user),
changes: vec![LdapModify {
operation: LdapModifyType::Replace,
modification: ldap3_proto::LdapPartialAttribute {
atype: "userPassword".to_string(),
vals: vec![b"tommy".to_vec(), b"other_value".to_vec()],
},
}],
}
};
assert_eq!(
ldap_handler.do_modify_request(&request).await,
make_modify_failure_response(
LdapResultCode::InvalidAttributeSyntax,
"Wrong number of values for password attribute: 2"
)
);
}
}
+28 -24
View File
@@ -198,6 +198,33 @@ pub mod tests {
make_bind_result(LdapResultCode::Success, "")
}
pub fn expect_password_change(mock: &mut MockTestBackendHandler, user: &str) {
use lldap_auth::{opaque, registration};
let mut rng = rand::rngs::OsRng;
let registration_start_request =
opaque::client::registration::start_registration("password".as_bytes(), &mut rng)
.unwrap();
let request = registration::ClientRegistrationStartRequest {
username: user.into(),
registration_start_request: registration_start_request.message,
};
let start_response = opaque::server::registration::start_registration(
&opaque::server::ServerSetup::new(&mut rng),
request.registration_start_request,
&request.username,
)
.unwrap();
mock.expect_registration_start().times(1).return_once(|_| {
Ok(registration::ServerRegistrationStartResponse {
server_data: "".to_string(),
registration_response: start_response.message,
})
});
mock.expect_registration_finish()
.times(1)
.return_once(|_| Ok(()));
}
#[tokio::test]
async fn test_bind() {
let mut mock = MockTestBackendHandler::new();
@@ -323,30 +350,7 @@ pub mod tests {
mock.expect_get_user_groups()
.with(eq(UserId::new("bob")))
.returning(|_| Ok(HashSet::new()));
use lldap_auth::*;
let mut rng = rand::rngs::OsRng;
let registration_start_request =
opaque::client::registration::start_registration("password".as_bytes(), &mut rng)
.unwrap();
let request = registration::ClientRegistrationStartRequest {
username: "bob".into(),
registration_start_request: registration_start_request.message,
};
let start_response = opaque::server::registration::start_registration(
&opaque::server::ServerSetup::new(&mut rng),
request.registration_start_request,
&request.username,
)
.unwrap();
mock.expect_registration_start().times(1).return_once(|_| {
Ok(registration::ServerRegistrationStartResponse {
server_data: "".to_string(),
registration_response: start_response.message,
})
});
mock.expect_registration_finish()
.times(1)
.return_once(|_| Ok(()));
expect_password_change(&mut mock, "bob");
let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = LdapOp::ExtendedRequest(
LdapPasswordModifyRequest {