mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	Add support for non-admin bind
This commit is contained in:
		
							parent
							
								
									31e8998ac3
								
							
						
					
					
						commit
						6abe94af13
					
				@ -27,6 +27,7 @@ tracing = "*"
 | 
			
		||||
tracing-actix-web = "0.3.0-beta.2"
 | 
			
		||||
tracing-log = "*"
 | 
			
		||||
tracing-subscriber = "*"
 | 
			
		||||
async-trait = "0.1.48"
 | 
			
		||||
 | 
			
		||||
[dependencies.figment]
 | 
			
		||||
features = ["toml", "env"]
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
use crate::infra::configuration::Configuration;
 | 
			
		||||
use anyhow::{bail, Result};
 | 
			
		||||
use async_trait::async_trait;
 | 
			
		||||
use sqlx::any::AnyPool;
 | 
			
		||||
use sqlx::Row;
 | 
			
		||||
 | 
			
		||||
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
 | 
			
		||||
pub struct BindRequest {
 | 
			
		||||
@ -24,9 +26,10 @@ pub struct User {
 | 
			
		||||
    pub creation_date: chrono::NaiveDateTime,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
pub trait BackendHandler: Clone + Send {
 | 
			
		||||
    fn bind(&mut self, request: BindRequest) -> Result<()>;
 | 
			
		||||
    fn list_users(&mut self, request: ListUsersRequest) -> Result<Vec<User>>;
 | 
			
		||||
    async fn bind(&mut self, request: BindRequest) -> Result<()>;
 | 
			
		||||
    async fn list_users(&mut self, request: ListUsersRequest) -> Result<Vec<User>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
@ -46,19 +49,34 @@ impl SqlBackendHandler {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn passwords_match(encrypted_password: &str, clear_password: &str) -> bool {
 | 
			
		||||
    encrypted_password == clear_password
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl BackendHandler for SqlBackendHandler {
 | 
			
		||||
    fn bind(&mut self, request: BindRequest) -> Result<()> {
 | 
			
		||||
        if request.name == self.config.ldap_user_dn
 | 
			
		||||
            && request.password == self.config.ldap_user_pass
 | 
			
		||||
        {
 | 
			
		||||
            self.authenticated = true;
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            bail!(r#"Authentication error for "{}""#, request.name)
 | 
			
		||||
    async fn bind(&mut self, request: BindRequest) -> Result<()> {
 | 
			
		||||
        if request.name == self.config.ldap_user_dn {
 | 
			
		||||
            if request.password == self.config.ldap_user_pass {
 | 
			
		||||
                self.authenticated = true;
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            } else {
 | 
			
		||||
                bail!(r#"Authentication error for "{}""#, request.name)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if let Ok(row) = sqlx::query("SELECT password FROM users WHERE user_id = ?")
 | 
			
		||||
            .bind(&request.name)
 | 
			
		||||
            .fetch_one(&self.sql_pool)
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            if passwords_match(&request.password, &row.get::<String, _>("password")) {
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        bail!(r#"Authentication error for "{}""#, request.name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn list_users(&mut self, request: ListUsersRequest) -> Result<Vec<User>> {
 | 
			
		||||
    async fn list_users(&mut self, request: ListUsersRequest) -> Result<Vec<User>> {
 | 
			
		||||
        Ok(Vec::new())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -69,8 +87,9 @@ mockall::mock! {
 | 
			
		||||
    impl Clone for TestBackendHandler {
 | 
			
		||||
        fn clone(&self) -> Self;
 | 
			
		||||
    }
 | 
			
		||||
    #[async_trait]
 | 
			
		||||
    impl BackendHandler for TestBackendHandler {
 | 
			
		||||
        fn bind(&mut self, request: BindRequest) -> Result<()>;
 | 
			
		||||
        fn list_users(&mut self, request: ListUsersRequest) -> Result<Vec<User>>;
 | 
			
		||||
        async fn bind(&mut self, request: BindRequest) -> Result<()>;
 | 
			
		||||
        async fn list_users(&mut self, request: ListUsersRequest) -> Result<Vec<User>>;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -82,10 +82,11 @@ pub struct LdapHandler<Backend: BackendHandler> {
 | 
			
		||||
    backend_handler: Backend,
 | 
			
		||||
    pub base_dn: Vec<(String, String)>,
 | 
			
		||||
    base_dn_str: String,
 | 
			
		||||
    ldap_user_dn: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<Backend: BackendHandler> LdapHandler<Backend> {
 | 
			
		||||
    pub fn new(backend_handler: Backend, ldap_base_dn: String) -> Self {
 | 
			
		||||
    pub fn new(backend_handler: Backend, ldap_base_dn: String, ldap_user_dn: String) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            dn: "Unauthenticated".to_string(),
 | 
			
		||||
            backend_handler,
 | 
			
		||||
@ -96,16 +97,19 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
 | 
			
		||||
                )
 | 
			
		||||
            }),
 | 
			
		||||
            base_dn_str: ldap_base_dn,
 | 
			
		||||
            ldap_user_dn,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg {
 | 
			
		||||
    pub async fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg {
 | 
			
		||||
        match self
 | 
			
		||||
            .backend_handler
 | 
			
		||||
            .bind(crate::domain::handler::BindRequest {
 | 
			
		||||
                name: sbr.dn.clone(),
 | 
			
		||||
                password: sbr.pw.clone(),
 | 
			
		||||
            }) {
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            Ok(()) => {
 | 
			
		||||
                self.dn = sbr.dn.clone();
 | 
			
		||||
                sbr.gen_success()
 | 
			
		||||
@ -114,7 +118,13 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn do_search(&mut self, lsr: &SearchRequest) -> Vec<LdapMsg> {
 | 
			
		||||
    pub async fn do_search(&mut self, lsr: &SearchRequest) -> Vec<LdapMsg> {
 | 
			
		||||
        if self.dn != self.ldap_user_dn {
 | 
			
		||||
            return vec![lsr.gen_error(
 | 
			
		||||
                LdapResultCode::InsufficentAccessRights,
 | 
			
		||||
                r#"Current user is not allowed to query LDAP"#.to_string(),
 | 
			
		||||
            )];
 | 
			
		||||
        }
 | 
			
		||||
        let dn_parts = match parse_distinguished_name(&lsr.base) {
 | 
			
		||||
            Ok(dn) => dn,
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
@ -128,7 +138,7 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
 | 
			
		||||
            // Search path is not in our tree, just return an empty success.
 | 
			
		||||
            return vec![lsr.gen_success()];
 | 
			
		||||
        }
 | 
			
		||||
        let users = match self.backend_handler.list_users(ListUsersRequest {}) {
 | 
			
		||||
        let users = match self.backend_handler.list_users(ListUsersRequest {}).await {
 | 
			
		||||
            Ok(users) => users,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                return vec![lsr.gen_error(
 | 
			
		||||
@ -156,10 +166,10 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn handle_ldap_message(&mut self, server_op: ServerOps) -> Option<Vec<LdapMsg>> {
 | 
			
		||||
    pub async fn handle_ldap_message(&mut self, server_op: ServerOps) -> Option<Vec<LdapMsg>> {
 | 
			
		||||
        let result = match server_op {
 | 
			
		||||
            ServerOps::SimpleBind(sbr) => vec![self.do_bind(&sbr)],
 | 
			
		||||
            ServerOps::Search(sr) => self.do_search(&sr),
 | 
			
		||||
            ServerOps::SimpleBind(sbr) => vec![self.do_bind(&sbr).await],
 | 
			
		||||
            ServerOps::Search(sr) => self.do_search(&sr).await,
 | 
			
		||||
            ServerOps::Unbind(_) => {
 | 
			
		||||
                // No need to notify on unbind (per rfc4511)
 | 
			
		||||
                return None;
 | 
			
		||||
@ -176,9 +186,10 @@ mod tests {
 | 
			
		||||
    use crate::domain::handler::MockTestBackendHandler;
 | 
			
		||||
    use chrono::NaiveDateTime;
 | 
			
		||||
    use mockall::predicate::eq;
 | 
			
		||||
    use tokio;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_bind() {
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_bind() {
 | 
			
		||||
        let mut mock = MockTestBackendHandler::new();
 | 
			
		||||
        mock.expect_bind()
 | 
			
		||||
            .with(eq(crate::domain::handler::BindRequest {
 | 
			
		||||
@ -187,7 +198,8 @@ mod tests {
 | 
			
		||||
            }))
 | 
			
		||||
            .times(1)
 | 
			
		||||
            .return_once(|_| Ok(()));
 | 
			
		||||
        let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
 | 
			
		||||
        let mut ldap_handler =
 | 
			
		||||
            LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
 | 
			
		||||
 | 
			
		||||
        let request = WhoamiRequest { msgid: 1 };
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
@ -200,7 +212,7 @@ mod tests {
 | 
			
		||||
            dn: "test".to_string(),
 | 
			
		||||
            pw: "pass".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        assert_eq!(ldap_handler.do_bind(&request), request.gen_success());
 | 
			
		||||
        assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
 | 
			
		||||
 | 
			
		||||
        let request = WhoamiRequest { msgid: 3 };
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
@ -209,6 +221,54 @@ mod tests {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_bind_invalid_credentials() {
 | 
			
		||||
        let mut mock = MockTestBackendHandler::new();
 | 
			
		||||
        mock.expect_bind()
 | 
			
		||||
            .with(eq(crate::domain::handler::BindRequest {
 | 
			
		||||
                name: "test".to_string(),
 | 
			
		||||
                password: "pass".to_string(),
 | 
			
		||||
            }))
 | 
			
		||||
            .times(1)
 | 
			
		||||
            .return_once(|_| Ok(()));
 | 
			
		||||
        let mut ldap_handler =
 | 
			
		||||
            LdapHandler::new(mock, "dc=example,dc=com".to_string(), "admin".to_string());
 | 
			
		||||
 | 
			
		||||
        let request = WhoamiRequest { msgid: 1 };
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            ldap_handler.do_whoami(&request),
 | 
			
		||||
            request.gen_operror("Unauthenticated")
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let request = SimpleBindRequest {
 | 
			
		||||
            msgid: 2,
 | 
			
		||||
            dn: "test".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: test")
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let request = SearchRequest {
 | 
			
		||||
            msgid: 2,
 | 
			
		||||
            base: "ou=people,dc=example,dc=com".to_string(),
 | 
			
		||||
            scope: LdapSearchScope::Base,
 | 
			
		||||
            filter: LdapFilter::And(vec![]),
 | 
			
		||||
            attrs: vec![],
 | 
			
		||||
        };
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            ldap_handler.do_search(&request).await,
 | 
			
		||||
            vec![request.gen_error(
 | 
			
		||||
                LdapResultCode::InsufficentAccessRights,
 | 
			
		||||
                r#"Current user is not allowed to query LDAP"#.to_string()
 | 
			
		||||
            )]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_is_subtree() {
 | 
			
		||||
        let subtree1 = &[
 | 
			
		||||
@ -237,8 +297,8 @@ mod tests {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_search() {
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_search() {
 | 
			
		||||
        let mut mock = MockTestBackendHandler::new();
 | 
			
		||||
        mock.expect_bind().return_once(|_| Ok(()));
 | 
			
		||||
        mock.expect_list_users()
 | 
			
		||||
@ -264,13 +324,14 @@ mod tests {
 | 
			
		||||
                    },
 | 
			
		||||
                ])
 | 
			
		||||
            });
 | 
			
		||||
        let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string());
 | 
			
		||||
        let mut ldap_handler =
 | 
			
		||||
            LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
 | 
			
		||||
        let request = SimpleBindRequest {
 | 
			
		||||
            msgid: 1,
 | 
			
		||||
            dn: "test".to_string(),
 | 
			
		||||
            pw: "pass".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        assert_eq!(ldap_handler.do_bind(&request), request.gen_success());
 | 
			
		||||
        assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
 | 
			
		||||
        let request = SearchRequest {
 | 
			
		||||
            msgid: 2,
 | 
			
		||||
            base: "ou=people,dc=example,dc=com".to_string(),
 | 
			
		||||
@ -286,7 +347,7 @@ mod tests {
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            ldap_handler.do_search(&request),
 | 
			
		||||
            ldap_handler.do_search(&request).await,
 | 
			
		||||
            vec![
 | 
			
		||||
                request.gen_result_entry(LdapSearchResultEntry {
 | 
			
		||||
                    dn: "cn=bob_1,dc=example,dc=com".to_string(),
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ async fn handle_incoming_message<Backend: BackendHandler>(
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    match session.handle_ldap_message(server_op) {
 | 
			
		||||
    match session.handle_ldap_message(server_op).await {
 | 
			
		||||
        None => return Ok(false),
 | 
			
		||||
        Some(result) => {
 | 
			
		||||
            for rmsg in result.into_iter() {
 | 
			
		||||
@ -62,20 +62,23 @@ where
 | 
			
		||||
    use futures_util::StreamExt;
 | 
			
		||||
 | 
			
		||||
    let ldap_base_dn = config.ldap_base_dn.clone();
 | 
			
		||||
    let ldap_user_dn = config.ldap_user_dn.clone();
 | 
			
		||||
    Ok(
 | 
			
		||||
        server_builder.bind("ldap", ("0.0.0.0", config.ldap_port), move || {
 | 
			
		||||
            let backend_handler = backend_handler.clone();
 | 
			
		||||
            let ldap_base_dn = ldap_base_dn.clone();
 | 
			
		||||
            let ldap_user_dn = ldap_user_dn.clone();
 | 
			
		||||
            pipeline_factory(fn_service(move |mut stream: TcpStream| {
 | 
			
		||||
                let backend_handler = backend_handler.clone();
 | 
			
		||||
                let ldap_base_dn = ldap_base_dn.clone();
 | 
			
		||||
                let ldap_user_dn = ldap_user_dn.clone();
 | 
			
		||||
                async move {
 | 
			
		||||
                    // Configure the codec etc.
 | 
			
		||||
                    let (r, w) = stream.split();
 | 
			
		||||
                    let mut requests = FramedRead::new(r, LdapCodec);
 | 
			
		||||
                    let mut resp = FramedWrite::new(w, LdapCodec);
 | 
			
		||||
 | 
			
		||||
                    let mut session = LdapHandler::new(backend_handler, ldap_base_dn);
 | 
			
		||||
                    let mut session = LdapHandler::new(backend_handler, ldap_base_dn, ldap_user_dn);
 | 
			
		||||
 | 
			
		||||
                    while let Some(msg) = requests.next().await {
 | 
			
		||||
                        if !handle_incoming_message(msg, &mut resp, &mut session).await? {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user