mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	ldap: handle full scope searches
Nextcloud searches for users by specifying the entire user DN as the scope. This commit adds support for these specific scopes.
This commit is contained in:
		
							parent
							
								
									da186fab38
								
							
						
					
					
						commit
						349aea5a03
					
				@ -48,6 +48,49 @@ fn parse_distinguished_name(dn: &str) -> Result<Vec<(String, String)>> {
 | 
			
		||||
        .collect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
enum SearchScope {
 | 
			
		||||
    Global,
 | 
			
		||||
    Users,
 | 
			
		||||
    Groups,
 | 
			
		||||
    User(LdapFilter),
 | 
			
		||||
    Group(LdapFilter),
 | 
			
		||||
    Unknown,
 | 
			
		||||
    Invalid,
 | 
			
		||||
}
 | 
			
		||||
fn get_search_scope(base_dn: &[(String, String)], dn_parts: &[(String, String)]) -> SearchScope {
 | 
			
		||||
    let base_dn_len = base_dn.len();
 | 
			
		||||
    if !is_subtree(dn_parts, base_dn) {
 | 
			
		||||
        SearchScope::Invalid
 | 
			
		||||
    } else if dn_parts.len() == base_dn_len {
 | 
			
		||||
        SearchScope::Global
 | 
			
		||||
    } else if dn_parts.len() == base_dn_len + 1
 | 
			
		||||
        && dn_parts[0] == ("ou".to_string(), "people".to_string())
 | 
			
		||||
    {
 | 
			
		||||
        SearchScope::Users
 | 
			
		||||
    } else if dn_parts.len() == base_dn_len + 1
 | 
			
		||||
        && dn_parts[0] == ("ou".to_string(), "groups".to_string())
 | 
			
		||||
    {
 | 
			
		||||
        SearchScope::Groups
 | 
			
		||||
    } else if dn_parts.len() == base_dn_len + 2
 | 
			
		||||
        && dn_parts[1] == ("ou".to_string(), "people".to_string())
 | 
			
		||||
    {
 | 
			
		||||
        SearchScope::User(LdapFilter::Equality(
 | 
			
		||||
            dn_parts[0].0.clone(),
 | 
			
		||||
            dn_parts[0].1.clone(),
 | 
			
		||||
        ))
 | 
			
		||||
    } else if dn_parts.len() == base_dn_len + 2
 | 
			
		||||
        && dn_parts[1] == ("ou".to_string(), "groups".to_string())
 | 
			
		||||
    {
 | 
			
		||||
        SearchScope::Group(LdapFilter::Equality(
 | 
			
		||||
            dn_parts[0].0.clone(),
 | 
			
		||||
            dn_parts[0].1.clone(),
 | 
			
		||||
        ))
 | 
			
		||||
    } else {
 | 
			
		||||
        SearchScope::Unknown
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn get_group_id_from_distinguished_name(
 | 
			
		||||
    dn: &str,
 | 
			
		||||
    base_tree: &[(String, String)],
 | 
			
		||||
@ -582,36 +625,50 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
			
		||||
                )]
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        if !is_subtree(&dn_parts, &self.base_dn) {
 | 
			
		||||
            // Search path is not in our tree, just return an empty success.
 | 
			
		||||
            warn!(
 | 
			
		||||
                "The specified search tree {:?} is not under the common subtree {:?}",
 | 
			
		||||
                &dn_parts, &self.base_dn
 | 
			
		||||
            );
 | 
			
		||||
            return vec![make_search_success()];
 | 
			
		||||
        }
 | 
			
		||||
        let mut results = Vec::new();
 | 
			
		||||
        let mut got_match = false;
 | 
			
		||||
        if dn_parts.len() == self.base_dn.len()
 | 
			
		||||
            || (dn_parts.len() == self.base_dn.len() + 1
 | 
			
		||||
                && dn_parts[0] == ("ou".to_string(), "people".to_string()))
 | 
			
		||||
        {
 | 
			
		||||
            got_match = true;
 | 
			
		||||
            results.extend(self.get_user_list(request, &user_filter).await);
 | 
			
		||||
        }
 | 
			
		||||
        if dn_parts.len() == self.base_dn.len()
 | 
			
		||||
            || (dn_parts.len() == self.base_dn.len() + 1
 | 
			
		||||
                && dn_parts[0] == ("ou".to_string(), "groups".to_string()))
 | 
			
		||||
        {
 | 
			
		||||
            got_match = true;
 | 
			
		||||
            results.extend(self.get_groups_list(request, &user_filter).await);
 | 
			
		||||
        }
 | 
			
		||||
        if !got_match {
 | 
			
		||||
            warn!(
 | 
			
		||||
                r#"The requested search tree "{}" matches neither the user subtree "ou=people,{}" nor the group subtree "ou=groups,{}""#,
 | 
			
		||||
                &request.base, &self.base_dn_str, &self.base_dn_str
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        let scope = get_search_scope(&self.base_dn, &dn_parts);
 | 
			
		||||
        let get_user_list = || async {
 | 
			
		||||
            self.get_user_list(&request.filter, &request.attrs, &request.base, &user_filter)
 | 
			
		||||
                .await
 | 
			
		||||
        };
 | 
			
		||||
        let get_group_list = || async {
 | 
			
		||||
            self.get_groups_list(&request.filter, &request.attrs, &request.base, &user_filter)
 | 
			
		||||
                .await
 | 
			
		||||
        };
 | 
			
		||||
        let mut results = match scope {
 | 
			
		||||
            SearchScope::Global => {
 | 
			
		||||
                let mut results = Vec::new();
 | 
			
		||||
                results.extend(get_user_list().await);
 | 
			
		||||
                results.extend(get_group_list().await);
 | 
			
		||||
                results
 | 
			
		||||
            }
 | 
			
		||||
            SearchScope::Users => get_user_list().await,
 | 
			
		||||
            SearchScope::Groups => get_group_list().await,
 | 
			
		||||
            SearchScope::User(filter) => {
 | 
			
		||||
                let filter = LdapFilter::And(vec![request.filter.clone(), filter]);
 | 
			
		||||
                self.get_user_list(&filter, &request.attrs, &request.base, &user_filter)
 | 
			
		||||
                    .await
 | 
			
		||||
            }
 | 
			
		||||
            SearchScope::Group(filter) => {
 | 
			
		||||
                let filter = LdapFilter::And(vec![request.filter.clone(), filter]);
 | 
			
		||||
                self.get_groups_list(&filter, &request.attrs, &request.base, &user_filter)
 | 
			
		||||
                    .await
 | 
			
		||||
            }
 | 
			
		||||
            SearchScope::Unknown => {
 | 
			
		||||
                warn!(
 | 
			
		||||
                    r#"The requested search tree "{}" matches neither the user subtree "ou=people,{}" nor the group subtree "ou=groups,{}""#,
 | 
			
		||||
                    &request.base, &self.base_dn_str, &self.base_dn_str
 | 
			
		||||
                );
 | 
			
		||||
                Vec::new()
 | 
			
		||||
            }
 | 
			
		||||
            SearchScope::Invalid => {
 | 
			
		||||
                // Search path is not in our tree, just return an empty success.
 | 
			
		||||
                warn!(
 | 
			
		||||
                    "The specified search tree {:?} is not under the common subtree {:?}",
 | 
			
		||||
                    &dn_parts, &self.base_dn
 | 
			
		||||
                );
 | 
			
		||||
                Vec::new()
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        if results.is_empty() || matches!(results[results.len() - 1], LdapOp::SearchResultEntry(_))
 | 
			
		||||
        {
 | 
			
		||||
            results.push(make_search_success());
 | 
			
		||||
@ -621,10 +678,12 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
			
		||||
 | 
			
		||||
    async fn get_user_list(
 | 
			
		||||
        &self,
 | 
			
		||||
        request: &LdapSearchRequest,
 | 
			
		||||
        filter: &LdapFilter,
 | 
			
		||||
        attributes: &[String],
 | 
			
		||||
        base: &str,
 | 
			
		||||
        user_filter: &Option<&UserId>,
 | 
			
		||||
    ) -> Vec<LdapOp> {
 | 
			
		||||
        let filters = match self.convert_user_filter(&request.filter) {
 | 
			
		||||
        let filters = match self.convert_user_filter(filter) {
 | 
			
		||||
            Ok(f) => f,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                return vec![make_search_error(
 | 
			
		||||
@ -639,8 +698,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
			
		||||
                UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        let expanded_attributes =
 | 
			
		||||
            expand_attribute_wildcards(&request.attrs, ALL_USER_ATTRIBUTE_KEYS);
 | 
			
		||||
        let expanded_attributes = expand_attribute_wildcards(attributes, ALL_USER_ATTRIBUTE_KEYS);
 | 
			
		||||
        let need_groups = expanded_attributes
 | 
			
		||||
            .iter()
 | 
			
		||||
            .any(|s| s.to_ascii_lowercase() == "memberof");
 | 
			
		||||
@ -653,7 +711,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                return vec![make_search_error(
 | 
			
		||||
                    LdapResultCode::Other,
 | 
			
		||||
                    format!(r#"Error during searching user "{}": {:#}"#, request.base, e),
 | 
			
		||||
                    format!(r#"Error while searching user "{}": {:#}"#, base, e),
 | 
			
		||||
                )]
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
@ -681,10 +739,12 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
			
		||||
 | 
			
		||||
    async fn get_groups_list(
 | 
			
		||||
        &self,
 | 
			
		||||
        request: &LdapSearchRequest,
 | 
			
		||||
        filter: &LdapFilter,
 | 
			
		||||
        attributes: &[String],
 | 
			
		||||
        base: &str,
 | 
			
		||||
        user_filter: &Option<&UserId>,
 | 
			
		||||
    ) -> Vec<LdapOp> {
 | 
			
		||||
        let filter = match self.convert_group_filter(&request.filter) {
 | 
			
		||||
        let filter = match self.convert_group_filter(filter) {
 | 
			
		||||
            Ok(f) => f,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                return vec![make_search_error(
 | 
			
		||||
@ -705,7 +765,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                return vec![make_search_error(
 | 
			
		||||
                    LdapResultCode::Other,
 | 
			
		||||
                    format!(r#"Error while listing groups "{}": {:#}"#, request.base, e),
 | 
			
		||||
                    format!(r#"Error while listing groups "{}": {:#}"#, base, e),
 | 
			
		||||
                )]
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
@ -716,7 +776,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
			
		||||
                make_ldap_search_group_result_entry(
 | 
			
		||||
                    u,
 | 
			
		||||
                    &self.base_dn_str,
 | 
			
		||||
                    &request.attrs,
 | 
			
		||||
                    attributes,
 | 
			
		||||
                    user_filter,
 | 
			
		||||
                    &self.ignored_group_attributes,
 | 
			
		||||
                )
 | 
			
		||||
@ -1180,6 +1240,37 @@ mod tests {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_search_user_as_scope() {
 | 
			
		||||
        let mut mock = MockTestBackendHandler::new();
 | 
			
		||||
        mock.expect_list_users()
 | 
			
		||||
            .with(
 | 
			
		||||
                eq(Some(UserRequestFilter::And(vec![
 | 
			
		||||
                    UserRequestFilter::And(vec![]),
 | 
			
		||||
                    UserRequestFilter::UserId(UserId::new("bob")),
 | 
			
		||||
                ]))),
 | 
			
		||||
                eq(false),
 | 
			
		||||
            )
 | 
			
		||||
            .times(1)
 | 
			
		||||
            .return_once(|_, _| Ok(vec![]));
 | 
			
		||||
        let mut ldap_handler = setup_bound_readonly_handler(mock).await;
 | 
			
		||||
 | 
			
		||||
        let request = LdapSearchRequest {
 | 
			
		||||
            base: "uid=bob,ou=people,Dc=example,dc=com".to_string(),
 | 
			
		||||
            scope: LdapSearchScope::Base,
 | 
			
		||||
            aliases: LdapDerefAliases::Never,
 | 
			
		||||
            sizelimit: 0,
 | 
			
		||||
            timelimit: 0,
 | 
			
		||||
            typesonly: false,
 | 
			
		||||
            filter: LdapFilter::And(vec![]),
 | 
			
		||||
            attrs: vec!["1.1".to_string()],
 | 
			
		||||
        };
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            ldap_handler.do_search(&request).await,
 | 
			
		||||
            vec![make_search_success()],
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_bind_invalid_dn() {
 | 
			
		||||
        let mock = MockTestBackendHandler::new();
 | 
			
		||||
@ -1573,6 +1664,34 @@ mod tests {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_search_group_as_scope() {
 | 
			
		||||
        let mut mock = MockTestBackendHandler::new();
 | 
			
		||||
        mock.expect_list_groups()
 | 
			
		||||
            .with(eq(Some(GroupRequestFilter::And(vec![
 | 
			
		||||
                GroupRequestFilter::And(vec![]),
 | 
			
		||||
                GroupRequestFilter::DisplayName("rockstars".to_string()),
 | 
			
		||||
            ]))))
 | 
			
		||||
            .times(1)
 | 
			
		||||
            .return_once(|_| Ok(vec![]));
 | 
			
		||||
        let mut ldap_handler = setup_bound_readonly_handler(mock).await;
 | 
			
		||||
 | 
			
		||||
        let request = LdapSearchRequest {
 | 
			
		||||
            base: "uid=rockstars,ou=groups,Dc=example,dc=com".to_string(),
 | 
			
		||||
            scope: LdapSearchScope::Base,
 | 
			
		||||
            aliases: LdapDerefAliases::Never,
 | 
			
		||||
            sizelimit: 0,
 | 
			
		||||
            timelimit: 0,
 | 
			
		||||
            typesonly: false,
 | 
			
		||||
            filter: LdapFilter::And(vec![]),
 | 
			
		||||
            attrs: vec!["1.1".to_string()],
 | 
			
		||||
        };
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            ldap_handler.do_search(&request).await,
 | 
			
		||||
            vec![make_search_success()],
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test]
 | 
			
		||||
    async fn test_search_groups_error() {
 | 
			
		||||
        let mut mock = MockTestBackendHandler::new();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user