mirror of
https://github.com/lldap/lldap.git
synced 2026-07-25 17:58:44 +00:00
server: split off password handling from ldap_handler
This commit is contained in:
committed by
nitnelave
parent
63f8b51c88
commit
37a85b4c2e
@@ -14,13 +14,15 @@ use crate::{
|
|||||||
access_control::{
|
access_control::{
|
||||||
AccessControlledBackendHandler, AdminBackendHandler, UserReadableBackendHandler,
|
AccessControlledBackendHandler, AdminBackendHandler, UserReadableBackendHandler,
|
||||||
},
|
},
|
||||||
ldap::search::{
|
ldap::{
|
||||||
self, is_root_dse_request, make_search_error, make_search_request, make_search_success,
|
password::{self, do_password_modification},
|
||||||
root_dse_response,
|
search::{
|
||||||
|
self, is_root_dse_request, make_search_error, make_search_request,
|
||||||
|
make_search_success, root_dse_response,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
|
||||||
use ldap3_proto::proto::{
|
use ldap3_proto::proto::{
|
||||||
LdapAddRequest, LdapAttribute, LdapBindRequest, LdapBindResponse, LdapCompareRequest,
|
LdapAddRequest, LdapAttribute, LdapBindRequest, LdapBindResponse, LdapCompareRequest,
|
||||||
LdapExtendedRequest, LdapExtendedResponse, LdapFilter, LdapModify, LdapModifyRequest,
|
LdapExtendedRequest, LdapExtendedResponse, LdapFilter, LdapModify, LdapModifyRequest,
|
||||||
@@ -36,8 +38,6 @@ use lldap_domain_handlers::handler::{BackendHandler, LoginHandler};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tracing::{debug, instrument};
|
use tracing::{debug, instrument};
|
||||||
|
|
||||||
use super::password;
|
|
||||||
|
|
||||||
fn make_add_error(code: LdapResultCode, message: String) -> LdapOp {
|
fn make_add_error(code: LdapResultCode, message: String) -> LdapOp {
|
||||||
LdapOp::AddResponse(LdapResultOp {
|
LdapOp::AddResponse(LdapResultOp {
|
||||||
code,
|
code,
|
||||||
@@ -47,7 +47,7 @@ fn make_add_error(code: LdapResultCode, message: String) -> LdapOp {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_extended_response(code: LdapResultCode, message: String) -> LdapOp {
|
pub(crate) fn make_extended_response(code: LdapResultCode, message: String) -> LdapOp {
|
||||||
LdapOp::ExtendedResponse(LdapExtendedResponse {
|
LdapOp::ExtendedResponse(LdapExtendedResponse {
|
||||||
res: LdapResultOp {
|
res: LdapResultOp {
|
||||||
code,
|
code,
|
||||||
@@ -60,7 +60,7 @@ fn make_extended_response(code: LdapResultCode, message: String) -> LdapOp {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_modify_response(code: LdapResultCode, message: String) -> LdapOp {
|
pub(crate) fn make_modify_response(code: LdapResultCode, message: String) -> LdapOp {
|
||||||
LdapOp::ModifyResponse(LdapResultOp {
|
LdapOp::ModifyResponse(LdapResultOp {
|
||||||
code,
|
code,
|
||||||
matcheddn: "".to_string(),
|
matcheddn: "".to_string(),
|
||||||
@@ -184,109 +184,30 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
})]
|
})]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn change_password<B: OpaqueHandler>(
|
|
||||||
&self,
|
|
||||||
backend_handler: &B,
|
|
||||||
user: UserId,
|
|
||||||
password: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
use lldap_auth::*;
|
|
||||||
let mut rng = rand::rngs::OsRng;
|
|
||||||
let registration_start_request =
|
|
||||||
opaque::client::registration::start_registration(password, &mut rng)?;
|
|
||||||
let req = registration::ClientRegistrationStartRequest {
|
|
||||||
username: user.clone(),
|
|
||||||
registration_start_request: registration_start_request.message,
|
|
||||||
};
|
|
||||||
let registration_start_response = backend_handler.registration_start(req).await?;
|
|
||||||
let registration_finish = opaque::client::registration::finish_registration(
|
|
||||||
registration_start_request.state,
|
|
||||||
registration_start_response.registration_response,
|
|
||||||
&mut rng,
|
|
||||||
)?;
|
|
||||||
let req = registration::ClientRegistrationFinishRequest {
|
|
||||||
server_data: registration_start_response.server_data,
|
|
||||||
registration_upload: registration_finish.message,
|
|
||||||
};
|
|
||||||
backend_handler.registration_finish(req).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn do_password_modification(
|
|
||||||
&mut self,
|
|
||||||
request: &LdapPasswordModifyRequest,
|
|
||||||
) -> LdapResult<Vec<LdapOp>> {
|
|
||||||
let credentials = self.user_info.as_ref().ok_or_else(|| LdapError {
|
|
||||||
code: LdapResultCode::InsufficentAccessRights,
|
|
||||||
message: "No user currently bound".to_string(),
|
|
||||||
})?;
|
|
||||||
match (&request.user_identity, &request.new_password) {
|
|
||||||
(Some(user), Some(password)) => {
|
|
||||||
match get_user_id_from_distinguished_name(
|
|
||||||
&user.to_ascii_lowercase(),
|
|
||||||
&self.ldap_info.base_dn,
|
|
||||||
&self.ldap_info.base_dn_str,
|
|
||||||
) {
|
|
||||||
Ok(uid) => {
|
|
||||||
let user_is_admin = self
|
|
||||||
.backend_handler
|
|
||||||
.get_readable_handler(credentials, &uid)
|
|
||||||
.expect("Unexpected permission error")
|
|
||||||
.get_user_groups(&uid)
|
|
||||||
.await
|
|
||||||
.map_err(|e| LdapError {
|
|
||||||
code: LdapResultCode::OperationsError,
|
|
||||||
message: format!(
|
|
||||||
"Internal error while requesting user's groups: {:#?}",
|
|
||||||
e
|
|
||||||
),
|
|
||||||
})?
|
|
||||||
.iter()
|
|
||||||
.any(|g| g.display_name == "lldap_admin".into());
|
|
||||||
if !credentials.can_change_password(&uid, user_is_admin) {
|
|
||||||
Err(LdapError {
|
|
||||||
code: LdapResultCode::InsufficentAccessRights,
|
|
||||||
message: format!(
|
|
||||||
r#"User `{}` cannot modify the password of user `{}`"#,
|
|
||||||
&credentials.user, &uid
|
|
||||||
),
|
|
||||||
})
|
|
||||||
} else if let Err(e) = self
|
|
||||||
.change_password(self.get_opaque_handler(), uid, password.as_bytes())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Err(LdapError {
|
|
||||||
code: LdapResultCode::Other,
|
|
||||||
message: format!("Error while changing the password: {:#?}", e),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(vec![make_extended_response(
|
|
||||||
LdapResultCode::Success,
|
|
||||||
"".to_string(),
|
|
||||||
)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(LdapError {
|
|
||||||
code: LdapResultCode::InvalidDNSyntax,
|
|
||||||
message: format!("Invalid username: {}", e),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(LdapError {
|
|
||||||
code: LdapResultCode::ConstraintViolation,
|
|
||||||
message: "Missing either user_id or password".to_string(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip_all, level = "debug")]
|
#[instrument(skip_all, level = "debug")]
|
||||||
async fn do_extended_request(&mut self, request: &LdapExtendedRequest) -> Vec<LdapOp> {
|
async fn do_extended_request(&mut self, request: &LdapExtendedRequest) -> Vec<LdapOp> {
|
||||||
match request.name.as_str() {
|
match request.name.as_str() {
|
||||||
OID_PASSWORD_MODIFY => match LdapPasswordModifyRequest::try_from(request) {
|
OID_PASSWORD_MODIFY => match LdapPasswordModifyRequest::try_from(request) {
|
||||||
Ok(password_request) => self
|
Ok(password_request) => {
|
||||||
.do_password_modification(&password_request)
|
let credentials = match self.user_info.as_ref() {
|
||||||
|
Some(user_id) => user_id,
|
||||||
|
None => {
|
||||||
|
return vec![make_extended_response(
|
||||||
|
LdapResultCode::InsufficentAccessRights,
|
||||||
|
"No user currently bound".to_string(),
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
do_password_modification(
|
||||||
|
credentials,
|
||||||
|
&self.ldap_info,
|
||||||
|
&self.backend_handler,
|
||||||
|
self.get_opaque_handler(),
|
||||||
|
&password_request,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e: LdapError| vec![make_extended_response(e.code, e.message)]),
|
.unwrap_or_else(|e: LdapError| vec![make_extended_response(e.code, e.message)])
|
||||||
|
}
|
||||||
Err(e) => vec![make_extended_response(
|
Err(e) => vec![make_extended_response(
|
||||||
LdapResultCode::ProtocolError,
|
LdapResultCode::ProtocolError,
|
||||||
format!("Error while parsing password modify request: {:#?}", e),
|
format!("Error while parsing password modify request: {:#?}", e),
|
||||||
@@ -344,7 +265,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if let [value] = &change.modification.vals.as_slice() {
|
if let [value] = &change.modification.vals.as_slice() {
|
||||||
self.change_password(self.get_opaque_handler(), user_id, value)
|
password::change_password(self.get_opaque_handler(), user_id, value)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| LdapError {
|
.map_err(|e| LdapError {
|
||||||
code: LdapResultCode::Other,
|
code: LdapResultCode::Other,
|
||||||
@@ -733,54 +654,6 @@ pub mod tests {
|
|||||||
setup_bound_handler_with_group(mock, "lldap_admin").await
|
setup_bound_handler_with_group(mock, "lldap_admin").await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_password_change() {
|
|
||||||
let mut mock = MockTestBackendHandler::new();
|
|
||||||
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(()));
|
|
||||||
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
|
||||||
let request = LdapOp::ExtendedRequest(
|
|
||||||
LdapPasswordModifyRequest {
|
|
||||||
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
|
||||||
old_password: None,
|
|
||||||
new_password: Some("password".to_string()),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ldap_handler.handle_ldap_message(request).await,
|
|
||||||
Some(vec![make_extended_response(
|
|
||||||
LdapResultCode::Success,
|
|
||||||
"".to_string(),
|
|
||||||
)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_whoami_empty() {
|
async fn test_whoami_empty() {
|
||||||
let mut ldap_handler =
|
let mut ldap_handler =
|
||||||
@@ -809,212 +682,6 @@ pub mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_password_change_modify_request() {
|
|
||||||
let mut mock = MockTestBackendHandler::new();
|
|
||||||
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(()));
|
|
||||||
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
|
||||||
let request = LdapOp::ModifyRequest(LdapModifyRequest {
|
|
||||||
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
|
|
||||||
changes: vec![LdapModify {
|
|
||||||
operation: LdapModifyType::Replace,
|
|
||||||
modification: LdapPartialAttribute {
|
|
||||||
atype: "userPassword".to_owned(),
|
|
||||||
vals: vec!["password".as_bytes().to_vec()],
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
assert_eq!(
|
|
||||||
ldap_handler.handle_ldap_message(request).await,
|
|
||||||
Some(vec![make_modify_response(
|
|
||||||
LdapResultCode::Success,
|
|
||||||
"".to_string(),
|
|
||||||
)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_password_change_password_manager() {
|
|
||||||
let mut mock = MockTestBackendHandler::new();
|
|
||||||
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(()));
|
|
||||||
let mut ldap_handler = setup_bound_password_manager_handler(mock).await;
|
|
||||||
let request = LdapOp::ExtendedRequest(
|
|
||||||
LdapPasswordModifyRequest {
|
|
||||||
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
|
||||||
old_password: None,
|
|
||||||
new_password: Some("password".to_string()),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ldap_handler.handle_ldap_message(request).await,
|
|
||||||
Some(vec![make_extended_response(
|
|
||||||
LdapResultCode::Success,
|
|
||||||
"".to_string(),
|
|
||||||
)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_password_change_errors() {
|
|
||||||
let mut mock = MockTestBackendHandler::new();
|
|
||||||
mock.expect_get_user_groups()
|
|
||||||
.with(eq(UserId::new("bob")))
|
|
||||||
.returning(|_| Ok(HashSet::new()));
|
|
||||||
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
|
||||||
let request = LdapOp::ExtendedRequest(
|
|
||||||
LdapPasswordModifyRequest {
|
|
||||||
user_identity: None,
|
|
||||||
old_password: None,
|
|
||||||
new_password: None,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ldap_handler.handle_ldap_message(request).await,
|
|
||||||
Some(vec![make_extended_response(
|
|
||||||
LdapResultCode::ConstraintViolation,
|
|
||||||
"Missing either user_id or password".to_string(),
|
|
||||||
)])
|
|
||||||
);
|
|
||||||
let request = LdapOp::ExtendedRequest(
|
|
||||||
LdapPasswordModifyRequest {
|
|
||||||
user_identity: Some("uid=bob,ou=groups,ou=people,dc=example,dc=com".to_string()),
|
|
||||||
old_password: None,
|
|
||||||
new_password: Some("password".to_string()),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ldap_handler.handle_ldap_message(request).await,
|
|
||||||
Some(vec![make_extended_response(
|
|
||||||
LdapResultCode::InvalidDNSyntax,
|
|
||||||
r#"Invalid username: Unexpected DN format. Got "uid=bob,ou=groups,ou=people,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#.to_string(),
|
|
||||||
)])
|
|
||||||
);
|
|
||||||
let request = LdapOp::ExtendedRequest(LdapExtendedRequest {
|
|
||||||
name: "test".to_string(),
|
|
||||||
value: None,
|
|
||||||
});
|
|
||||||
assert_eq!(
|
|
||||||
ldap_handler.handle_ldap_message(request).await,
|
|
||||||
Some(vec![make_extended_response(
|
|
||||||
LdapResultCode::UnwillingToPerform,
|
|
||||||
"Unsupported extended operation: test".to_string(),
|
|
||||||
)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_password_change_unauthorized_password_manager() {
|
|
||||||
let mut mock = MockTestBackendHandler::new();
|
|
||||||
let mut groups = HashSet::new();
|
|
||||||
groups.insert(GroupDetails {
|
|
||||||
group_id: GroupId(0),
|
|
||||||
display_name: "lldap_admin".into(),
|
|
||||||
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
|
||||||
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
|
||||||
attributes: Vec::new(),
|
|
||||||
});
|
|
||||||
mock.expect_get_user_groups()
|
|
||||||
.with(eq(UserId::new("bob")))
|
|
||||||
.times(1)
|
|
||||||
.return_once(|_| Ok(groups));
|
|
||||||
let mut ldap_handler = setup_bound_password_manager_handler(mock).await;
|
|
||||||
let request = LdapOp::ExtendedRequest(
|
|
||||||
LdapPasswordModifyRequest {
|
|
||||||
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
|
||||||
old_password: Some("pass".to_string()),
|
|
||||||
new_password: Some("password".to_string()),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ldap_handler.handle_ldap_message(request).await,
|
|
||||||
Some(vec![make_extended_response(
|
|
||||||
LdapResultCode::InsufficentAccessRights,
|
|
||||||
"User `test` cannot modify the password of user `bob`".to_string(),
|
|
||||||
)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_password_change_unauthorized_readonly() {
|
|
||||||
let mut mock = MockTestBackendHandler::new();
|
|
||||||
mock.expect_get_user_groups()
|
|
||||||
.with(eq(UserId::new("bob")))
|
|
||||||
.times(1)
|
|
||||||
.return_once(|_| Ok(HashSet::new()));
|
|
||||||
let mut ldap_handler = setup_bound_readonly_handler(mock).await;
|
|
||||||
let request = LdapOp::ExtendedRequest(
|
|
||||||
LdapPasswordModifyRequest {
|
|
||||||
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
|
||||||
old_password: Some("pass".to_string()),
|
|
||||||
new_password: Some("password".to_string()),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
ldap_handler.handle_ldap_message(request).await,
|
|
||||||
Some(vec![make_extended_response(
|
|
||||||
LdapResultCode::InsufficentAccessRights,
|
|
||||||
"User `test` cannot modify the password of user `bob`".to_string(),
|
|
||||||
)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_create_user() {
|
async fn test_create_user() {
|
||||||
let mut mock = MockTestBackendHandler::new();
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
use crate::domain::ldap::{
|
use crate::{
|
||||||
error::{LdapError, LdapResult},
|
domain::{
|
||||||
utils::{LdapInfo, get_user_id_from_distinguished_name},
|
ldap::{
|
||||||
|
error::{LdapError, LdapResult},
|
||||||
|
utils::{LdapInfo, get_user_id_from_distinguished_name},
|
||||||
|
},
|
||||||
|
opaque_handler::OpaqueHandler,
|
||||||
|
},
|
||||||
|
infra::{
|
||||||
|
access_control::{AccessControlledBackendHandler, UserReadableBackendHandler},
|
||||||
|
ldap::handler::make_extended_response,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use ldap3_proto::proto::{LdapBindCred, LdapBindRequest, LdapResultCode};
|
use anyhow::Result;
|
||||||
|
use ldap3_proto::proto::{
|
||||||
|
LdapBindCred, LdapBindRequest, LdapOp, LdapPasswordModifyRequest, LdapResultCode,
|
||||||
|
};
|
||||||
|
use lldap_auth::access_control::ValidationResults;
|
||||||
use lldap_domain::types::UserId;
|
use lldap_domain::types::UserId;
|
||||||
use lldap_domain_handlers::handler::{BindRequest, LoginHandler};
|
use lldap_domain_handlers::handler::{BackendHandler, BindRequest, LoginHandler};
|
||||||
|
|
||||||
pub(crate) async fn do_bind(
|
pub(crate) async fn do_bind(
|
||||||
ldap_info: &LdapInfo,
|
ldap_info: &LdapInfo,
|
||||||
@@ -53,13 +66,116 @@ pub(crate) async fn do_bind(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn change_password<B: OpaqueHandler>(
|
||||||
|
backend_handler: &B,
|
||||||
|
user: UserId,
|
||||||
|
password: &[u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
use lldap_auth::*;
|
||||||
|
let mut rng = rand::rngs::OsRng;
|
||||||
|
let registration_start_request =
|
||||||
|
opaque::client::registration::start_registration(password, &mut rng)?;
|
||||||
|
let req = registration::ClientRegistrationStartRequest {
|
||||||
|
username: user.clone(),
|
||||||
|
registration_start_request: registration_start_request.message,
|
||||||
|
};
|
||||||
|
let registration_start_response = backend_handler.registration_start(req).await?;
|
||||||
|
let registration_finish = opaque::client::registration::finish_registration(
|
||||||
|
registration_start_request.state,
|
||||||
|
registration_start_response.registration_response,
|
||||||
|
&mut rng,
|
||||||
|
)?;
|
||||||
|
let req = registration::ClientRegistrationFinishRequest {
|
||||||
|
server_data: registration_start_response.server_data,
|
||||||
|
registration_upload: registration_finish.message,
|
||||||
|
};
|
||||||
|
backend_handler.registration_finish(req).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn do_password_modification<Handler: BackendHandler>(
|
||||||
|
credentials: &ValidationResults,
|
||||||
|
ldap_info: &LdapInfo,
|
||||||
|
backend_handler: &AccessControlledBackendHandler<Handler>,
|
||||||
|
opaque_handler: &impl OpaqueHandler,
|
||||||
|
request: &LdapPasswordModifyRequest,
|
||||||
|
) -> LdapResult<Vec<LdapOp>> {
|
||||||
|
match (&request.user_identity, &request.new_password) {
|
||||||
|
(Some(user), Some(password)) => {
|
||||||
|
match get_user_id_from_distinguished_name(
|
||||||
|
&user.to_ascii_lowercase(),
|
||||||
|
&ldap_info.base_dn,
|
||||||
|
&ldap_info.base_dn_str,
|
||||||
|
) {
|
||||||
|
Ok(uid) => {
|
||||||
|
let user_is_admin = backend_handler
|
||||||
|
.get_readable_handler(credentials, &uid)
|
||||||
|
.expect("Unexpected permission error")
|
||||||
|
.get_user_groups(&uid)
|
||||||
|
.await
|
||||||
|
.map_err(|e| LdapError {
|
||||||
|
code: LdapResultCode::OperationsError,
|
||||||
|
message: format!(
|
||||||
|
"Internal error while requesting user's groups: {:#?}",
|
||||||
|
e
|
||||||
|
),
|
||||||
|
})?
|
||||||
|
.iter()
|
||||||
|
.any(|g| g.display_name == "lldap_admin".into());
|
||||||
|
if !credentials.can_change_password(&uid, user_is_admin) {
|
||||||
|
Err(LdapError {
|
||||||
|
code: LdapResultCode::InsufficentAccessRights,
|
||||||
|
message: format!(
|
||||||
|
r#"User `{}` cannot modify the password of user `{}`"#,
|
||||||
|
&credentials.user, &uid
|
||||||
|
),
|
||||||
|
})
|
||||||
|
} else if let Err(e) =
|
||||||
|
change_password(opaque_handler, uid, password.as_bytes()).await
|
||||||
|
{
|
||||||
|
Err(LdapError {
|
||||||
|
code: LdapResultCode::Other,
|
||||||
|
message: format!("Error while changing the password: {:#?}", e),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(vec![make_extended_response(
|
||||||
|
LdapResultCode::Success,
|
||||||
|
"".to_string(),
|
||||||
|
)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Err(LdapError {
|
||||||
|
code: LdapResultCode::InvalidDNSyntax,
|
||||||
|
message: format!("Invalid username: {}", e),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(LdapError {
|
||||||
|
code: LdapResultCode::ConstraintViolation,
|
||||||
|
message: "Missing either user_id or password".to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::infra::ldap::handler::LdapHandler;
|
use crate::infra::{
|
||||||
use crate::infra::test_utils::MockTestBackendHandler;
|
ldap::handler::{
|
||||||
|
LdapHandler, make_modify_response,
|
||||||
|
tests::{
|
||||||
|
setup_bound_admin_handler, setup_bound_password_manager_handler,
|
||||||
|
setup_bound_readonly_handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test_utils::MockTestBackendHandler,
|
||||||
|
};
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
use ldap3_proto::proto::{LdapBindResponse, LdapOp, LdapResult as LdapResultOp};
|
use ldap3_proto::proto::{
|
||||||
|
LdapBindResponse, LdapModify, LdapModifyRequest, LdapModifyType, LdapOp,
|
||||||
|
LdapResult as LdapResultOp,
|
||||||
|
};
|
||||||
|
use ldap3_proto::{LdapPartialAttribute, proto::LdapExtendedRequest};
|
||||||
use lldap_domain::{types::*, uuid};
|
use lldap_domain::{types::*, uuid};
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
@@ -102,10 +218,7 @@ pub mod tests {
|
|||||||
cred: LdapBindCred::Simple("pass".to_string()),
|
cred: LdapBindCred::Simple("pass".to_string()),
|
||||||
});
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ldap_handler
|
ldap_handler.handle_ldap_message(request).await.unwrap(),
|
||||||
.handle_ldap_message(request)
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
make_bind_success()
|
make_bind_success()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -139,8 +252,7 @@ pub mod tests {
|
|||||||
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
dn: "uid=test,ou=people,dc=example,dc=com".to_string(),
|
||||||
cred: LdapBindCred::Simple("pass".to_string()),
|
cred: LdapBindCred::Simple("pass".to_string()),
|
||||||
};
|
};
|
||||||
assert_eq!(ldap_handler.do_bind(&request).await,
|
assert_eq!(ldap_handler.do_bind(&request).await, make_bind_success());
|
||||||
make_bind_success());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -154,7 +266,10 @@ pub mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ldap_handler.do_bind(&request).await,
|
ldap_handler.do_bind(&request).await,
|
||||||
make_bind_result(LdapResultCode::NamingViolation, r#"Unexpected DN format. Got "cn=bob,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#),
|
make_bind_result(
|
||||||
|
LdapResultCode::NamingViolation,
|
||||||
|
r#"Unexpected DN format. Got "cn=bob,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let request = LdapBindRequest {
|
let request = LdapBindRequest {
|
||||||
dn: "uid=bob,dc=example,dc=com".to_string(),
|
dn: "uid=bob,dc=example,dc=com".to_string(),
|
||||||
@@ -162,7 +277,10 @@ pub mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ldap_handler.do_bind(&request).await,
|
ldap_handler.do_bind(&request).await,
|
||||||
make_bind_result(LdapResultCode::NamingViolation, r#"Unexpected DN format. Got "uid=bob,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#),
|
make_bind_result(
|
||||||
|
LdapResultCode::NamingViolation,
|
||||||
|
r#"Unexpected DN format. Got "uid=bob,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let request = LdapBindRequest {
|
let request = LdapBindRequest {
|
||||||
dn: "uid=bob,ou=groups,dc=example,dc=com".to_string(),
|
dn: "uid=bob,ou=groups,dc=example,dc=com".to_string(),
|
||||||
@@ -170,7 +288,10 @@ pub mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ldap_handler.do_bind(&request).await,
|
ldap_handler.do_bind(&request).await,
|
||||||
make_bind_result(LdapResultCode::NamingViolation, r#"Unexpected DN format. Got "uid=bob,ou=groups,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#),
|
make_bind_result(
|
||||||
|
LdapResultCode::NamingViolation,
|
||||||
|
r#"Unexpected DN format. Got "uid=bob,ou=groups,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let request = LdapBindRequest {
|
let request = LdapBindRequest {
|
||||||
dn: "uid=bob,ou=people,dc=example,dc=fr".to_string(),
|
dn: "uid=bob,ou=people,dc=example,dc=fr".to_string(),
|
||||||
@@ -178,7 +299,10 @@ pub mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ldap_handler.do_bind(&request).await,
|
ldap_handler.do_bind(&request).await,
|
||||||
make_bind_result(LdapResultCode::NamingViolation, r#"Not a subtree of the base tree"#),
|
make_bind_result(
|
||||||
|
LdapResultCode::NamingViolation,
|
||||||
|
r#"Not a subtree of the base tree"#
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let request = LdapBindRequest {
|
let request = LdapBindRequest {
|
||||||
dn: "uid=bob=test,ou=people,dc=example,dc=com".to_string(),
|
dn: "uid=bob=test,ou=people,dc=example,dc=com".to_string(),
|
||||||
@@ -186,7 +310,264 @@ pub mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ldap_handler.do_bind(&request).await,
|
ldap_handler.do_bind(&request).await,
|
||||||
make_bind_result(LdapResultCode::NamingViolation, r#"Too many elements in distinguished name: "uid", "bob", "test""#),
|
make_bind_result(
|
||||||
|
LdapResultCode::NamingViolation,
|
||||||
|
r#"Too many elements in distinguished name: "uid", "bob", "test""#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_password_change() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
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(()));
|
||||||
|
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
||||||
|
let request = LdapOp::ExtendedRequest(
|
||||||
|
LdapPasswordModifyRequest {
|
||||||
|
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
||||||
|
old_password: None,
|
||||||
|
new_password: Some("password".to_string()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.handle_ldap_message(request).await,
|
||||||
|
Some(vec![make_extended_response(
|
||||||
|
LdapResultCode::Success,
|
||||||
|
"".to_string(),
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_password_change_modify_request() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
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(()));
|
||||||
|
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
||||||
|
let request = LdapOp::ModifyRequest(LdapModifyRequest {
|
||||||
|
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
|
||||||
|
changes: vec![LdapModify {
|
||||||
|
operation: LdapModifyType::Replace,
|
||||||
|
modification: LdapPartialAttribute {
|
||||||
|
atype: "userPassword".to_owned(),
|
||||||
|
vals: vec!["password".as_bytes().to_vec()],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.handle_ldap_message(request).await,
|
||||||
|
Some(vec![make_modify_response(
|
||||||
|
LdapResultCode::Success,
|
||||||
|
"".to_string(),
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_password_change_password_manager() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
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(()));
|
||||||
|
let mut ldap_handler = setup_bound_password_manager_handler(mock).await;
|
||||||
|
let request = LdapOp::ExtendedRequest(
|
||||||
|
LdapPasswordModifyRequest {
|
||||||
|
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
||||||
|
old_password: None,
|
||||||
|
new_password: Some("password".to_string()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.handle_ldap_message(request).await,
|
||||||
|
Some(vec![make_extended_response(
|
||||||
|
LdapResultCode::Success,
|
||||||
|
"".to_string(),
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_password_change_errors() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
mock.expect_get_user_groups()
|
||||||
|
.with(eq(UserId::new("bob")))
|
||||||
|
.returning(|_| Ok(HashSet::new()));
|
||||||
|
let mut ldap_handler = setup_bound_admin_handler(mock).await;
|
||||||
|
let request = LdapOp::ExtendedRequest(
|
||||||
|
LdapPasswordModifyRequest {
|
||||||
|
user_identity: None,
|
||||||
|
old_password: None,
|
||||||
|
new_password: None,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.handle_ldap_message(request).await,
|
||||||
|
Some(vec![make_extended_response(
|
||||||
|
LdapResultCode::ConstraintViolation,
|
||||||
|
"Missing either user_id or password".to_string(),
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
let request = LdapOp::ExtendedRequest(
|
||||||
|
LdapPasswordModifyRequest {
|
||||||
|
user_identity: Some("uid=bob,ou=groups,ou=people,dc=example,dc=com".to_string()),
|
||||||
|
old_password: None,
|
||||||
|
new_password: Some("password".to_string()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.handle_ldap_message(request).await,
|
||||||
|
Some(vec![make_extended_response(
|
||||||
|
LdapResultCode::InvalidDNSyntax,
|
||||||
|
r#"Invalid username: Unexpected DN format. Got "uid=bob,ou=groups,ou=people,dc=example,dc=com", expected: "uid=id,ou=people,dc=example,dc=com""#.to_string(),
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
let request = LdapOp::ExtendedRequest(LdapExtendedRequest {
|
||||||
|
name: "test".to_string(),
|
||||||
|
value: None,
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.handle_ldap_message(request).await,
|
||||||
|
Some(vec![make_extended_response(
|
||||||
|
LdapResultCode::UnwillingToPerform,
|
||||||
|
"Unsupported extended operation: test".to_string(),
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_password_change_unauthorized_password_manager() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
let mut groups = HashSet::new();
|
||||||
|
groups.insert(GroupDetails {
|
||||||
|
group_id: GroupId(0),
|
||||||
|
display_name: "lldap_admin".into(),
|
||||||
|
creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(),
|
||||||
|
uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||||
|
attributes: Vec::new(),
|
||||||
|
});
|
||||||
|
mock.expect_get_user_groups()
|
||||||
|
.with(eq(UserId::new("bob")))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_| Ok(groups));
|
||||||
|
let mut ldap_handler = setup_bound_password_manager_handler(mock).await;
|
||||||
|
let request = LdapOp::ExtendedRequest(
|
||||||
|
LdapPasswordModifyRequest {
|
||||||
|
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
||||||
|
old_password: Some("pass".to_string()),
|
||||||
|
new_password: Some("password".to_string()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.handle_ldap_message(request).await,
|
||||||
|
Some(vec![make_extended_response(
|
||||||
|
LdapResultCode::InsufficentAccessRights,
|
||||||
|
"User `test` cannot modify the password of user `bob`".to_string(),
|
||||||
|
)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_password_change_unauthorized_readonly() {
|
||||||
|
let mut mock = MockTestBackendHandler::new();
|
||||||
|
mock.expect_get_user_groups()
|
||||||
|
.with(eq(UserId::new("bob")))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_| Ok(HashSet::new()));
|
||||||
|
let mut ldap_handler = setup_bound_readonly_handler(mock).await;
|
||||||
|
let request = LdapOp::ExtendedRequest(
|
||||||
|
LdapPasswordModifyRequest {
|
||||||
|
user_identity: Some("uid=bob,ou=people,dc=example,dc=com".to_string()),
|
||||||
|
old_password: Some("pass".to_string()),
|
||||||
|
new_password: Some("password".to_string()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ldap_handler.handle_ldap_message(request).await,
|
||||||
|
Some(vec![make_extended_response(
|
||||||
|
LdapResultCode::InsufficentAccessRights,
|
||||||
|
"User `test` cannot modify the password of user `bob`".to_string(),
|
||||||
|
)])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user