Translate the LDAP DN into user IDs

This commit is contained in:
Valentin Tolmer 2021-05-13 19:31:37 +02:00
parent 7e76d3aae2
commit b49a03fd87
3 changed files with 127 additions and 9 deletions

View File

@ -160,6 +160,11 @@ impl BackendHandler for SqlBackendHandler {
} }
async fn get_user_groups(&self, user: String) -> Result<HashSet<String>> { async fn get_user_groups(&self, user: String) -> Result<HashSet<String>> {
if user == self.config.ldap_user_dn {
let mut groups = HashSet::new();
groups.insert("lldap_admin".to_string());
return Ok(groups);
}
let query: String = Query::select() let query: String = Query::select()
.column(Groups::DisplayName) .column(Groups::DisplayName)
.from(Groups::Table) .from(Groups::Table)

View File

@ -30,7 +30,8 @@ impl Default for Configuration {
secret_pepper: String::from("secretsecretpepper"), secret_pepper: String::from("secretsecretpepper"),
jwt_secret: String::from("secretjwtsecret"), jwt_secret: String::from("secretjwtsecret"),
ldap_base_dn: String::from("dc=example,dc=com"), ldap_base_dn: String::from("dc=example,dc=com"),
ldap_user_dn: String::from("cn=admin,dc=example,dc=com"), // cn=admin,dc=example,dc=com
ldap_user_dn: String::from("admin"),
ldap_user_pass: String::from("password"), ldap_user_pass: String::from("password"),
database_url: String::from("sqlite://users.db?mode=rwc"), database_url: String::from("sqlite://users.db?mode=rwc"),
verbose: false, verbose: false,

View File

@ -29,6 +29,37 @@ fn parse_distinguished_name(dn: &str) -> Result<Vec<(String, String)>> {
.collect() .collect()
} }
fn get_user_id_from_distinguished_name(
dn: &str,
base_tree: &[(String, String)],
base_dn_str: &str,
ldap_user_dn: &str,
) -> Result<String> {
let parts = parse_distinguished_name(dn)?;
if !is_subtree(&parts, base_tree) {
bail!("Not a subtree of the base tree");
}
if parts.len() == base_tree.len() + 1 {
if dn != ldap_user_dn {
bail!(r#"Wrong admin DN. Expected: "{}""#, ldap_user_dn);
}
Ok(parts[0].1.to_string())
} else if parts.len() == base_tree.len() + 2 {
if parts[1].0 != "ou" || parts[1].1 != "people" || parts[0].0 != "cn" {
bail!(
r#"Unexpected user DN format. Expected: "cn=username,ou=people,{}""#,
base_dn_str
);
}
Ok(parts[0].1.to_string())
} else {
bail!(
r#"Unexpected user DN format. Expected: "cn=username,ou=people,{}""#,
base_dn_str
);
}
}
fn get_attribute(user: &User, attribute: &str) -> Result<Vec<String>> { fn get_attribute(user: &User, attribute: &str) -> Result<Vec<String>> {
match attribute { match attribute {
"objectClass" => Ok(vec![ "objectClass" => Ok(vec![
@ -132,16 +163,25 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
ldap_base_dn ldap_base_dn
) )
}), }),
ldap_user_dn: format!("cn={},{}", ldap_user_dn, &ldap_base_dn),
base_dn_str: ldap_base_dn, base_dn_str: ldap_base_dn,
ldap_user_dn,
} }
} }
pub async fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg { pub async fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg {
let user_id = match get_user_id_from_distinguished_name(
&sbr.dn,
&self.base_dn,
&self.base_dn_str,
&self.ldap_user_dn,
) {
Ok(s) => s,
Err(e) => return sbr.gen_error(LdapResultCode::NamingViolation, e.to_string()),
};
match self match self
.backend_handler .backend_handler
.bind(crate::domain::handler::BindRequest { .bind(crate::domain::handler::BindRequest {
name: sbr.dn.clone(), name: user_id,
password: sbr.pw.clone(), password: sbr.pw.clone(),
}) })
.await .await
@ -232,6 +272,7 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::domain::handler::BindRequest;
use crate::domain::handler::MockTestBackendHandler; use crate::domain::handler::MockTestBackendHandler;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use mockall::predicate::eq; use mockall::predicate::eq;
@ -240,12 +281,17 @@ mod tests {
async fn setup_bound_handler( async fn setup_bound_handler(
mut mock: MockTestBackendHandler, mut mock: MockTestBackendHandler,
) -> LdapHandler<MockTestBackendHandler> { ) -> LdapHandler<MockTestBackendHandler> {
mock.expect_bind().return_once(|_| Ok(())); mock.expect_bind()
.with(eq(BindRequest {
name: "test".to_string(),
password: "pass".to_string(),
}))
.return_once(|_| Ok(()));
let mut ldap_handler = let mut ldap_handler =
LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string()); LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
let request = SimpleBindRequest { let request = SimpleBindRequest {
msgid: 1, msgid: 1,
dn: "test".to_string(), dn: "cn=test,dc=example,dc=com".to_string(),
pw: "pass".to_string(), pw: "pass".to_string(),
}; };
ldap_handler.do_bind(&request).await; ldap_handler.do_bind(&request).await;
@ -254,6 +300,39 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_bind() { async fn test_bind() {
let mut mock = MockTestBackendHandler::new();
mock.expect_bind()
.with(eq(crate::domain::handler::BindRequest {
name: "bob".to_string(),
password: "pass".to_string(),
}))
.times(1)
.return_once(|_| Ok(()));
let mut ldap_handler =
LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
let request = WhoamiRequest { msgid: 1 };
assert_eq!(
ldap_handler.do_whoami(&request),
request.gen_operror("Unauthenticated")
);
let request = SimpleBindRequest {
msgid: 2,
dn: "cn=bob,ou=people,dc=example,dc=com".to_string(),
pw: "pass".to_string(),
};
assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
let request = WhoamiRequest { msgid: 3 };
assert_eq!(
ldap_handler.do_whoami(&request),
request.gen_success("dn: cn=bob,ou=people,dc=example,dc=com")
);
}
#[tokio::test]
async fn test_admin_bind() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_bind() mock.expect_bind()
.with(eq(crate::domain::handler::BindRequest { .with(eq(crate::domain::handler::BindRequest {
@ -273,7 +352,7 @@ mod tests {
let request = SimpleBindRequest { let request = SimpleBindRequest {
msgid: 2, msgid: 2,
dn: "test".to_string(), dn: "cn=test,dc=example,dc=com".to_string(),
pw: "pass".to_string(), pw: "pass".to_string(),
}; };
assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success()); assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
@ -281,7 +360,7 @@ mod tests {
let request = WhoamiRequest { msgid: 3 }; let request = WhoamiRequest { msgid: 3 };
assert_eq!( assert_eq!(
ldap_handler.do_whoami(&request), ldap_handler.do_whoami(&request),
request.gen_success("dn: test") request.gen_success("dn: cn=test,dc=example,dc=com")
); );
} }
@ -306,7 +385,7 @@ mod tests {
let request = SimpleBindRequest { let request = SimpleBindRequest {
msgid: 2, msgid: 2,
dn: "test".to_string(), dn: "cn=test,ou=people,dc=example,dc=com".to_string(),
pw: "pass".to_string(), pw: "pass".to_string(),
}; };
assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success()); assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
@ -314,7 +393,7 @@ mod tests {
let request = WhoamiRequest { msgid: 3 }; let request = WhoamiRequest { msgid: 3 };
assert_eq!( assert_eq!(
ldap_handler.do_whoami(&request), ldap_handler.do_whoami(&request),
request.gen_success("dn: test") request.gen_success("dn: cn=test,ou=people,dc=example,dc=com")
); );
let request = SearchRequest { let request = SearchRequest {
@ -333,6 +412,39 @@ mod tests {
); );
} }
#[tokio::test]
async fn test_bind_invalid_dn() {
let mock = MockTestBackendHandler::new();
let mut ldap_handler =
LdapHandler::new(mock, "dc=example,dc=com".to_string(), "admin".to_string());
let request = SimpleBindRequest {
msgid: 2,
dn: "cn=bob,dc=example,dc=com".to_string(),
pw: "pass".to_string(),
};
assert_eq!(
ldap_handler.do_bind(&request).await,
request.gen_error(
LdapResultCode::NamingViolation,
r#"Wrong admin DN. Expected: "cn=admin,dc=example,dc=com""#.to_string()
)
);
let request = SimpleBindRequest {
msgid: 2,
dn: "cn=bob,ou=groups,dc=example,dc=com".to_string(),
pw: "pass".to_string(),
};
assert_eq!(
ldap_handler.do_bind(&request).await,
request.gen_error(
LdapResultCode::NamingViolation,
r#"Unexpected user DN format. Expected: "cn=username,ou=people,dc=example,dc=com""#
.to_string()
)
);
}
#[test] #[test]
fn test_is_subtree() { fn test_is_subtree() {
let subtree1 = &[ let subtree1 = &[