mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	Merge branch 'main' into main
This commit is contained in:
		
						commit
						060a082368
					
				
							
								
								
									
										5
									
								
								.github/workflows/rust.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/rust.yml
									
									
									
									
										vendored
									
									
								
							@ -20,10 +20,12 @@ jobs:
 | 
				
			|||||||
      - id: skip_check
 | 
					      - id: skip_check
 | 
				
			||||||
        uses: fkirc/skip-duplicate-actions@master
 | 
					        uses: fkirc/skip-duplicate-actions@master
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          concurrent_skipping: 'never'
 | 
					          concurrent_skipping: 'outdated_runs'
 | 
				
			||||||
          skip_after_successful_duplicate: 'true'
 | 
					          skip_after_successful_duplicate: 'true'
 | 
				
			||||||
          paths_ignore: '["**/*.md", "**/docs/**", "example_configs/**", "*.sh"]'
 | 
					          paths_ignore: '["**/*.md", "**/docs/**", "example_configs/**", "*.sh"]'
 | 
				
			||||||
          do_not_skip: '["workflow_dispatch", "schedule"]'
 | 
					          do_not_skip: '["workflow_dispatch", "schedule"]'
 | 
				
			||||||
 | 
					          cancel_others: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test:
 | 
					  test:
 | 
				
			||||||
    name: cargo test
 | 
					    name: cargo test
 | 
				
			||||||
    needs: pre_job
 | 
					    needs: pre_job
 | 
				
			||||||
@ -97,6 +99,7 @@ jobs:
 | 
				
			|||||||
  coverage:
 | 
					  coverage:
 | 
				
			||||||
    name: Code coverage
 | 
					    name: Code coverage
 | 
				
			||||||
    needs: pre_job
 | 
					    needs: pre_job
 | 
				
			||||||
 | 
					    if: ${{ needs.pre_job.outputs.should_skip != 'true' || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout sources
 | 
					      - name: Checkout sources
 | 
				
			||||||
 | 
				
			|||||||
@ -100,7 +100,7 @@ impl Component for App {
 | 
				
			|||||||
        html! {
 | 
					        html! {
 | 
				
			||||||
            <div class="container shadow-sm py-3">
 | 
					            <div class="container shadow-sm py-3">
 | 
				
			||||||
              {self.view_banner()}
 | 
					              {self.view_banner()}
 | 
				
			||||||
              <div class="row justify-content-center">
 | 
					              <div class="row justify-content-center" style="padding-bottom: 80px;">
 | 
				
			||||||
                <div class="shadow-sm py-3" style="max-width: 1000px">
 | 
					                <div class="shadow-sm py-3" style="max-width: 1000px">
 | 
				
			||||||
                  <Router<AppRoute>
 | 
					                  <Router<AppRoute>
 | 
				
			||||||
                    render = Router::render(move |s| Self::dispatch_route(s, &link, is_admin))
 | 
					                    render = Router::render(move |s| Self::dispatch_route(s, &link, is_admin))
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										90
									
								
								example_configs/dolibarr.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								example_configs/dolibarr.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					# Configuration pour Dolibarr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This example will help you to create user in dolibarr from your users in your lldap server from a specific group and to login with the password from the lldap server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## To connect ldap->dolibarr 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In Dolibarr, install the LDAP module from Home -> Modules/Applications
 | 
				
			||||||
 | 
					Go to the configuration of this module and fill it like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 - Users and groups synchronization: `LDAP -> Dolibarr`
 | 
				
			||||||
 | 
					 - Contacts' synchronization: `No`
 | 
				
			||||||
 | 
					 - Type: `OpenLdap`
 | 
				
			||||||
 | 
					 - Version: `Version 3`
 | 
				
			||||||
 | 
					 - Primary server: `ldap://example.com`
 | 
				
			||||||
 | 
					 - Secondary server: `Empty`
 | 
				
			||||||
 | 
					 - Server port: port `3890`
 | 
				
			||||||
 | 
					 - Server DN: `dc=example,dc=com`
 | 
				
			||||||
 | 
					 - Use TLS:  `No`
 | 
				
			||||||
 | 
					 - Administrator DN: `cn=admin,ou=people,dc=example,dc=com`
 | 
				
			||||||
 | 
					 - Administrator password: `secret`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Click on modify then "TEST LDAP CONNECTION". 
 | 
				
			||||||
 | 
					You should get this result on the bottom:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					 TCP connect to LDAP server successful (Server=ldap://example.com, Port=389)
 | 
				
			||||||
 | 
					Connect/Authenticate to LDAP server successful (Server=ldap://example.com, Port=389, Admin=cn=admin,ou=people,dc=example,dc=com, Password=**********)
 | 
				
			||||||
 | 
					LDAP server configured for version 3
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					And two new tabs will appear on the top:
 | 
				
			||||||
 | 
					Users and Groups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We will use only Users in this example to get the users we want to import.
 | 
				
			||||||
 | 
					The tab Groups would be to import groups.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Click on the Users tab and fill it like this:
 | 
				
			||||||
 | 
					 - Users' DN: `ou=people,dc=example,dc=com`
 | 
				
			||||||
 | 
					 - List of objectClass: `person`
 | 
				
			||||||
 | 
					 - Search filter: `memberOf=cn=yournamegroup,ou=groups,dc=example,dc=com`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(or if you don't have a group for your users, leave the search filter empty)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 - Full name: `cn`
 | 
				
			||||||
 | 
					 - Name: `sn`
 | 
				
			||||||
 | 
					 - First name: `givenname`
 | 
				
			||||||
 | 
					 - Login `uid`
 | 
				
			||||||
 | 
					 - Email address `mail`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Click on "MODIFY" and then on "TEST A LDAP SEARCH"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You should get the number of users in the group or all users if you didn't use a filter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## To import ldap users into the dolibarr database (needed to login with those users):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Navigate to Users & Groups -> New Users
 | 
				
			||||||
 | 
					Click on the blank form "Users in LDAP database", you will get the list of the users in the group filled above. With the "GET" button, you will import the selected user.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## To enable LDAP login:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Modify your `conf.php` in your dolibarr folder in `htdocs/conf`
 | 
				
			||||||
 | 
					Replace
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					// Authentication settings
 | 
				
			||||||
 | 
					$dolibarr_main_authentication='dolibarr'; 
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					with:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					// Authentication settings
 | 
				
			||||||
 | 
					// Only  add "ldap" to only login using the ldap server, or/and "dolibar" to compare with local users. In any case, you need to have the user existing in dolibarr.
 | 
				
			||||||
 | 
					$dolibarr_main_authentication='ldap,dolibarr'; 
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_host='ldap://127.0.0.1:3890';
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_port='3890';
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_version='3';
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_servertype='openldap';
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_login_attribute='cn';
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_dn='ou=people,dc=example,dc=com';
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_admin_login='cn=admin,ou=people,dc=example,dc=com';
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_admin_pass='secret';
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can add this line to enable debug in case anything is wrong:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$dolibarr_main_auth_ldap_debug='true';
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -13,6 +13,9 @@ use ldap3_server::proto::{
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
use log::{debug, warn};
 | 
					use log::{debug, warn};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, PartialEq, Eq, Clone)]
 | 
				
			||||||
 | 
					struct LdapDn(String);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn make_dn_pair<I>(mut iter: I) -> Result<(String, String)>
 | 
					fn make_dn_pair<I>(mut iter: I) -> Result<(String, String)>
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    I: Iterator<Item = String>,
 | 
					    I: Iterator<Item = String>,
 | 
				
			||||||
@ -94,24 +97,24 @@ fn get_user_id_from_distinguished_name(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_user_attribute(user: &User, attribute: &str, dn: &str) -> Result<Vec<String>> {
 | 
					fn get_user_attribute(user: &User, attribute: &str, dn: &str) -> Result<Option<Vec<String>>> {
 | 
				
			||||||
    match attribute.to_lowercase().as_str() {
 | 
					    Ok(Some(match attribute.to_lowercase().as_str() {
 | 
				
			||||||
        "objectclass" => Ok(vec![
 | 
					        "objectclass" => vec![
 | 
				
			||||||
            "inetOrgPerson".to_string(),
 | 
					            "inetOrgPerson".to_string(),
 | 
				
			||||||
            "posixAccount".to_string(),
 | 
					            "posixAccount".to_string(),
 | 
				
			||||||
            "mailAccount".to_string(),
 | 
					            "mailAccount".to_string(),
 | 
				
			||||||
            "person".to_string(),
 | 
					            "person".to_string(),
 | 
				
			||||||
        ]),
 | 
					        ],
 | 
				
			||||||
        "dn" => Ok(vec![dn.to_string()]),
 | 
					        "dn" => vec![dn.to_string()],
 | 
				
			||||||
        "uid" => Ok(vec![user.user_id.to_string()]),
 | 
					        "uid" => vec![user.user_id.to_string()],
 | 
				
			||||||
        "mail" => Ok(vec![user.email.clone()]),
 | 
					        "mail" => vec![user.email.clone()],
 | 
				
			||||||
        "givenname" => Ok(vec![user.first_name.clone()]),
 | 
					        "givenname" => vec![user.first_name.clone()],
 | 
				
			||||||
        "sn" => Ok(vec![user.last_name.clone()]),
 | 
					        "sn" => vec![user.last_name.clone()],
 | 
				
			||||||
        "cn" | "displayname" => Ok(vec![user.display_name.clone()]),
 | 
					        "cn" | "displayname" => vec![user.display_name.clone()],
 | 
				
			||||||
        "createtimestamp" | "modifytimestamp" => Ok(vec![user.creation_date.to_rfc3339()]),
 | 
					        "createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()],
 | 
				
			||||||
        "1.1" => Ok(vec![]),
 | 
					        "1.1" => return Ok(None),
 | 
				
			||||||
        _ => bail!("Unsupported user attribute: {}", attribute),
 | 
					        _ => bail!("Unsupported user attribute: {}", attribute),
 | 
				
			||||||
    }
 | 
					    }))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn make_ldap_search_user_result_entry(
 | 
					fn make_ldap_search_user_result_entry(
 | 
				
			||||||
@ -124,47 +127,63 @@ fn make_ldap_search_user_result_entry(
 | 
				
			|||||||
        dn: dn.clone(),
 | 
					        dn: dn.clone(),
 | 
				
			||||||
        attributes: attributes
 | 
					        attributes: attributes
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .map(|a| {
 | 
					            .filter_map(|a| {
 | 
				
			||||||
                Ok(LdapPartialAttribute {
 | 
					                let values = match get_user_attribute(&user, a, &dn) {
 | 
				
			||||||
 | 
					                    Err(e) => return Some(Err(e)),
 | 
				
			||||||
 | 
					                    Ok(v) => v,
 | 
				
			||||||
 | 
					                }?;
 | 
				
			||||||
 | 
					                Some(Ok(LdapPartialAttribute {
 | 
				
			||||||
                    atype: a.to_string(),
 | 
					                    atype: a.to_string(),
 | 
				
			||||||
                    vals: get_user_attribute(&user, a, &dn)?,
 | 
					                    vals: values,
 | 
				
			||||||
                })
 | 
					                }))
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .collect::<Result<Vec<LdapPartialAttribute>>>()?,
 | 
					            .collect::<Result<Vec<LdapPartialAttribute>>>()?,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_group_attribute(group: &Group, base_dn_str: &str, attribute: &str) -> Result<Vec<String>> {
 | 
					fn get_group_attribute(
 | 
				
			||||||
    match attribute.to_lowercase().as_str() {
 | 
					    group: &Group,
 | 
				
			||||||
        "objectclass" => Ok(vec!["groupOfUniqueNames".to_string()]),
 | 
					    base_dn_str: &str,
 | 
				
			||||||
        "dn" => Ok(vec![format!(
 | 
					    attribute: &str,
 | 
				
			||||||
 | 
					    user_filter: &Option<&UserId>,
 | 
				
			||||||
 | 
					) -> Result<Option<Vec<String>>> {
 | 
				
			||||||
 | 
					    Ok(Some(match attribute.to_lowercase().as_str() {
 | 
				
			||||||
 | 
					        "objectclass" => vec!["groupOfUniqueNames".to_string()],
 | 
				
			||||||
 | 
					        "dn" => vec![format!(
 | 
				
			||||||
            "cn={},ou=groups,{}",
 | 
					            "cn={},ou=groups,{}",
 | 
				
			||||||
            group.display_name, base_dn_str
 | 
					            group.display_name, base_dn_str
 | 
				
			||||||
        )]),
 | 
					        )],
 | 
				
			||||||
        "cn" | "uid" => Ok(vec![group.display_name.clone()]),
 | 
					        "cn" | "uid" => vec![group.display_name.clone()],
 | 
				
			||||||
        "member" | "uniquemember" => Ok(group
 | 
					        "member" | "uniquemember" => group
 | 
				
			||||||
            .users
 | 
					            .users
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .filter(|u| user_filter.map(|f| *u == f).unwrap_or(true))
 | 
				
			||||||
            .map(|u| format!("cn={},ou=people,{}", u, base_dn_str))
 | 
					            .map(|u| format!("cn={},ou=people,{}", u, base_dn_str))
 | 
				
			||||||
            .collect()),
 | 
					            .collect(),
 | 
				
			||||||
 | 
					        "1.1" => return Ok(None),
 | 
				
			||||||
        _ => bail!("Unsupported group attribute: {}", attribute),
 | 
					        _ => bail!("Unsupported group attribute: {}", attribute),
 | 
				
			||||||
    }
 | 
					    }))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn make_ldap_search_group_result_entry(
 | 
					fn make_ldap_search_group_result_entry(
 | 
				
			||||||
    group: Group,
 | 
					    group: Group,
 | 
				
			||||||
    base_dn_str: &str,
 | 
					    base_dn_str: &str,
 | 
				
			||||||
    attributes: &[String],
 | 
					    attributes: &[String],
 | 
				
			||||||
 | 
					    user_filter: &Option<&UserId>,
 | 
				
			||||||
) -> Result<LdapSearchResultEntry> {
 | 
					) -> Result<LdapSearchResultEntry> {
 | 
				
			||||||
    Ok(LdapSearchResultEntry {
 | 
					    Ok(LdapSearchResultEntry {
 | 
				
			||||||
        dn: format!("cn={},ou=groups,{}", group.display_name, base_dn_str),
 | 
					        dn: format!("cn={},ou=groups,{}", group.display_name, base_dn_str),
 | 
				
			||||||
        attributes: attributes
 | 
					        attributes: attributes
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .map(|a| {
 | 
					            .filter_map(|a| {
 | 
				
			||||||
                Ok(LdapPartialAttribute {
 | 
					                let values = match get_group_attribute(&group, base_dn_str, a, user_filter) {
 | 
				
			||||||
 | 
					                    Err(e) => return Some(Err(e)),
 | 
				
			||||||
 | 
					                    Ok(v) => v,
 | 
				
			||||||
 | 
					                }?;
 | 
				
			||||||
 | 
					                Some(Ok(LdapPartialAttribute {
 | 
				
			||||||
                    atype: a.to_string(),
 | 
					                    atype: a.to_string(),
 | 
				
			||||||
                    vals: get_group_attribute(&group, base_dn_str, a)?,
 | 
					                    vals: values,
 | 
				
			||||||
                })
 | 
					                }))
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .collect::<Result<Vec<LdapPartialAttribute>>>()?,
 | 
					            .collect::<Result<Vec<LdapPartialAttribute>>>()?,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
@ -265,17 +284,19 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct LdapHandler<Backend: BackendHandler + LoginHandler + OpaqueHandler> {
 | 
					pub struct LdapHandler<Backend: BackendHandler + LoginHandler + OpaqueHandler> {
 | 
				
			||||||
    dn: UserId,
 | 
					    dn: LdapDn,
 | 
				
			||||||
 | 
					    user_id: UserId,
 | 
				
			||||||
    backend_handler: Backend,
 | 
					    backend_handler: Backend,
 | 
				
			||||||
    pub base_dn: Vec<(String, String)>,
 | 
					    pub base_dn: Vec<(String, String)>,
 | 
				
			||||||
    base_dn_str: String,
 | 
					    base_dn_str: String,
 | 
				
			||||||
    ldap_user_dn: UserId,
 | 
					    ldap_user_dn: LdapDn,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend> {
 | 
					impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend> {
 | 
				
			||||||
    pub fn new(backend_handler: Backend, ldap_base_dn: String, ldap_user_dn: UserId) -> Self {
 | 
					    pub fn new(backend_handler: Backend, ldap_base_dn: String, ldap_user_dn: UserId) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            dn: UserId::new("unauthenticated"),
 | 
					            dn: LdapDn("unauthenticated".to_string()),
 | 
				
			||||||
 | 
					            user_id: UserId::new("unauthenticated"),
 | 
				
			||||||
            backend_handler,
 | 
					            backend_handler,
 | 
				
			||||||
            base_dn: parse_distinguished_name(&ldap_base_dn).unwrap_or_else(|_| {
 | 
					            base_dn: parse_distinguished_name(&ldap_base_dn).unwrap_or_else(|_| {
 | 
				
			||||||
                panic!(
 | 
					                panic!(
 | 
				
			||||||
@ -283,7 +304,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
                    ldap_base_dn
 | 
					                    ldap_base_dn
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }),
 | 
					            }),
 | 
				
			||||||
            ldap_user_dn: UserId::new(&format!("cn={},ou=people,{}", ldap_user_dn, &ldap_base_dn)),
 | 
					            ldap_user_dn: LdapDn(format!("cn={},ou=people,{}", ldap_user_dn, &ldap_base_dn)),
 | 
				
			||||||
            base_dn_str: ldap_base_dn,
 | 
					            base_dn_str: ldap_base_dn,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -302,13 +323,14 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
        match self
 | 
					        match self
 | 
				
			||||||
            .backend_handler
 | 
					            .backend_handler
 | 
				
			||||||
            .bind(BindRequest {
 | 
					            .bind(BindRequest {
 | 
				
			||||||
                name: user_id,
 | 
					                name: user_id.clone(),
 | 
				
			||||||
                password: password.clone(),
 | 
					                password: password.clone(),
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Ok(()) => {
 | 
					            Ok(()) => {
 | 
				
			||||||
                self.dn = UserId::new(&request.dn);
 | 
					                self.dn = LdapDn(request.dn.clone());
 | 
				
			||||||
 | 
					                self.user_id = user_id;
 | 
				
			||||||
                (LdapResultCode::Success, "".to_string())
 | 
					                (LdapResultCode::Success, "".to_string())
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(_) => (LdapResultCode::InvalidCredentials, "".to_string()),
 | 
					            Err(_) => (LdapResultCode::InvalidCredentials, "".to_string()),
 | 
				
			||||||
@ -382,15 +404,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn do_search(&mut self, request: &LdapSearchRequest) -> Vec<LdapOp> {
 | 
					    pub async fn do_search(&mut self, request: &LdapSearchRequest) -> Vec<LdapOp> {
 | 
				
			||||||
        if self.dn != self.ldap_user_dn {
 | 
					        let admin = self.dn == self.ldap_user_dn;
 | 
				
			||||||
            return vec![make_search_error(
 | 
					 | 
				
			||||||
                LdapResultCode::InsufficentAccessRights,
 | 
					 | 
				
			||||||
                format!(
 | 
					 | 
				
			||||||
                    r#"Current user `{}` is not allowed to query LDAP, expected {}"#,
 | 
					 | 
				
			||||||
                    &self.dn, &self.ldap_user_dn
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
            )];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if request.base.is_empty()
 | 
					        if request.base.is_empty()
 | 
				
			||||||
            && request.scope == LdapSearchScope::Base
 | 
					            && request.scope == LdapSearchScope::Base
 | 
				
			||||||
            && request.filter == LdapFilter::Present("objectClass".to_string())
 | 
					            && request.filter == LdapFilter::Present("objectClass".to_string())
 | 
				
			||||||
@ -418,19 +432,20 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        let mut results = Vec::new();
 | 
					        let mut results = Vec::new();
 | 
				
			||||||
        let mut got_match = false;
 | 
					        let mut got_match = false;
 | 
				
			||||||
 | 
					        let user_filter = if admin { None } else { Some(&self.user_id) };
 | 
				
			||||||
        if dn_parts.len() == self.base_dn.len()
 | 
					        if dn_parts.len() == self.base_dn.len()
 | 
				
			||||||
            || (dn_parts.len() == self.base_dn.len() + 1
 | 
					            || (dn_parts.len() == self.base_dn.len() + 1
 | 
				
			||||||
                && dn_parts[0] == ("ou".to_string(), "people".to_string()))
 | 
					                && dn_parts[0] == ("ou".to_string(), "people".to_string()))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            got_match = true;
 | 
					            got_match = true;
 | 
				
			||||||
            results.extend(self.get_user_list(request).await);
 | 
					            results.extend(self.get_user_list(request, &user_filter).await);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if dn_parts.len() == self.base_dn.len()
 | 
					        if dn_parts.len() == self.base_dn.len()
 | 
				
			||||||
            || (dn_parts.len() == self.base_dn.len() + 1
 | 
					            || (dn_parts.len() == self.base_dn.len() + 1
 | 
				
			||||||
                && dn_parts[0] == ("ou".to_string(), "groups".to_string()))
 | 
					                && dn_parts[0] == ("ou".to_string(), "groups".to_string()))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            got_match = true;
 | 
					            got_match = true;
 | 
				
			||||||
            results.extend(self.get_groups_list(request).await);
 | 
					            results.extend(self.get_groups_list(request, &user_filter).await);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if !got_match {
 | 
					        if !got_match {
 | 
				
			||||||
            warn!(
 | 
					            warn!(
 | 
				
			||||||
@ -445,9 +460,13 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
        results
 | 
					        results
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn get_user_list(&self, request: &LdapSearchRequest) -> Vec<LdapOp> {
 | 
					    async fn get_user_list(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        request: &LdapSearchRequest,
 | 
				
			||||||
 | 
					        user_filter: &Option<&UserId>,
 | 
				
			||||||
 | 
					    ) -> Vec<LdapOp> {
 | 
				
			||||||
        let filters = match self.convert_user_filter(&request.filter) {
 | 
					        let filters = match self.convert_user_filter(&request.filter) {
 | 
				
			||||||
            Ok(f) => Some(f),
 | 
					            Ok(f) => f,
 | 
				
			||||||
            Err(e) => {
 | 
					            Err(e) => {
 | 
				
			||||||
                return vec![make_search_error(
 | 
					                return vec![make_search_error(
 | 
				
			||||||
                    LdapResultCode::UnwillingToPerform,
 | 
					                    LdapResultCode::UnwillingToPerform,
 | 
				
			||||||
@ -455,7 +474,13 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
                )]
 | 
					                )]
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let users = match self.backend_handler.list_users(filters).await {
 | 
					        let filters = match user_filter {
 | 
				
			||||||
 | 
					            None => filters,
 | 
				
			||||||
 | 
					            Some(u) => {
 | 
				
			||||||
 | 
					                UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        let users = match self.backend_handler.list_users(Some(filters)).await {
 | 
				
			||||||
            Ok(users) => users,
 | 
					            Ok(users) => users,
 | 
				
			||||||
            Err(e) => {
 | 
					            Err(e) => {
 | 
				
			||||||
                return vec![make_search_error(
 | 
					                return vec![make_search_error(
 | 
				
			||||||
@ -478,7 +503,11 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
            })
 | 
					            })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn get_groups_list(&self, request: &LdapSearchRequest) -> Vec<LdapOp> {
 | 
					    async fn get_groups_list(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        request: &LdapSearchRequest,
 | 
				
			||||||
 | 
					        user_filter: &Option<&UserId>,
 | 
				
			||||||
 | 
					    ) -> Vec<LdapOp> {
 | 
				
			||||||
        let filter = match self.convert_group_filter(&request.filter) {
 | 
					        let filter = match self.convert_group_filter(&request.filter) {
 | 
				
			||||||
            Ok(f) => f,
 | 
					            Ok(f) => f,
 | 
				
			||||||
            Err(e) => {
 | 
					            Err(e) => {
 | 
				
			||||||
@ -488,6 +517,12 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
                )]
 | 
					                )]
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					        let filter = match user_filter {
 | 
				
			||||||
 | 
					            None => filter,
 | 
				
			||||||
 | 
					            Some(u) => {
 | 
				
			||||||
 | 
					                GroupRequestFilter::And(vec![filter, GroupRequestFilter::Member((*u).clone())])
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let groups = match self.backend_handler.list_groups(Some(filter)).await {
 | 
					        let groups = match self.backend_handler.list_groups(Some(filter)).await {
 | 
				
			||||||
            Ok(groups) => groups,
 | 
					            Ok(groups) => groups,
 | 
				
			||||||
@ -501,7 +536,14 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        groups
 | 
					        groups
 | 
				
			||||||
            .into_iter()
 | 
					            .into_iter()
 | 
				
			||||||
            .map(|u| make_ldap_search_group_result_entry(u, &self.base_dn_str, &request.attrs))
 | 
					            .map(|u| {
 | 
				
			||||||
 | 
					                make_ldap_search_group_result_entry(
 | 
				
			||||||
 | 
					                    u,
 | 
				
			||||||
 | 
					                    &self.base_dn_str,
 | 
				
			||||||
 | 
					                    &request.attrs,
 | 
				
			||||||
 | 
					                    user_filter,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
            .map(|entry| Ok(LdapOp::SearchResultEntry(entry?)))
 | 
					            .map(|entry| Ok(LdapOp::SearchResultEntry(entry?)))
 | 
				
			||||||
            .collect::<Result<Vec<_>>>()
 | 
					            .collect::<Result<Vec<_>>>()
 | 
				
			||||||
            .unwrap_or_else(|e| {
 | 
					            .unwrap_or_else(|e| {
 | 
				
			||||||
@ -528,7 +570,8 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            LdapOp::SearchRequest(request) => self.do_search(&request).await,
 | 
					            LdapOp::SearchRequest(request) => self.do_search(&request).await,
 | 
				
			||||||
            LdapOp::UnbindRequest => {
 | 
					            LdapOp::UnbindRequest => {
 | 
				
			||||||
                self.dn = UserId::new("unauthenticated");
 | 
					                self.dn = LdapDn("unauthenticated".to_string());
 | 
				
			||||||
 | 
					                self.user_id = UserId::new("unauthenticated");
 | 
				
			||||||
                // No need to notify on unbind (per rfc4511)
 | 
					                // No need to notify on unbind (per rfc4511)
 | 
				
			||||||
                return None;
 | 
					                return None;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -801,7 +844,7 @@ mod tests {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[tokio::test]
 | 
					    #[tokio::test]
 | 
				
			||||||
    async fn test_bind_invalid_credentials() {
 | 
					    async fn test_search_non_admin_user() {
 | 
				
			||||||
        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 {
 | 
				
			||||||
@ -810,6 +853,18 @@ mod tests {
 | 
				
			|||||||
            }))
 | 
					            }))
 | 
				
			||||||
            .times(1)
 | 
					            .times(1)
 | 
				
			||||||
            .return_once(|_| Ok(()));
 | 
					            .return_once(|_| Ok(()));
 | 
				
			||||||
 | 
					        mock.expect_list_users()
 | 
				
			||||||
 | 
					            .with(eq(Some(UserRequestFilter::And(vec![
 | 
				
			||||||
 | 
					                UserRequestFilter::And(vec![]),
 | 
				
			||||||
 | 
					                UserRequestFilter::UserId(UserId::new("test")),
 | 
				
			||||||
 | 
					            ]))))
 | 
				
			||||||
 | 
					            .times(1)
 | 
				
			||||||
 | 
					            .return_once(|_| {
 | 
				
			||||||
 | 
					                Ok(vec![User {
 | 
				
			||||||
 | 
					                    user_id: UserId::new("test"),
 | 
				
			||||||
 | 
					                    ..Default::default()
 | 
				
			||||||
 | 
					                }])
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        let mut ldap_handler =
 | 
					        let mut ldap_handler =
 | 
				
			||||||
            LdapHandler::new(mock, "dc=example,dc=com".to_string(), UserId::new("admin"));
 | 
					            LdapHandler::new(mock, "dc=example,dc=com".to_string(), UserId::new("admin"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -822,13 +877,17 @@ mod tests {
 | 
				
			|||||||
            LdapResultCode::Success
 | 
					            LdapResultCode::Success
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let request = make_user_search_request::<String>(LdapFilter::And(vec![]), vec![]);
 | 
					        let request =
 | 
				
			||||||
 | 
					            make_user_search_request::<String>(LdapFilter::And(vec![]), vec!["1.1".to_string()]);
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            ldap_handler.do_search(&request).await,
 | 
					            ldap_handler.do_search(&request).await,
 | 
				
			||||||
            vec![make_search_error(
 | 
					            vec![
 | 
				
			||||||
                LdapResultCode::InsufficentAccessRights,
 | 
					                LdapOp::SearchResultEntry(LdapSearchResultEntry {
 | 
				
			||||||
                r#"Current user `cn=test,ou=people,dc=example,dc=com` is not allowed to query LDAP, expected cn=admin,ou=people,dc=example,dc=com"#.to_string()
 | 
					                    dn: "cn=test,ou=people,dc=example,dc=com".to_string(),
 | 
				
			||||||
            )]
 | 
					                    attributes: vec![],
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                make_search_success()
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1144,17 +1203,14 @@ mod tests {
 | 
				
			|||||||
                LdapFilter::Equality("objectclass".to_string(), "groupOfUniqueNames".to_string()),
 | 
					                LdapFilter::Equality("objectclass".to_string(), "groupOfUniqueNames".to_string()),
 | 
				
			||||||
                LdapFilter::Equality("objectclass".to_string(), "groupOfNames".to_string()),
 | 
					                LdapFilter::Equality("objectclass".to_string(), "groupOfNames".to_string()),
 | 
				
			||||||
            ]),
 | 
					            ]),
 | 
				
			||||||
            vec!["cn"],
 | 
					            vec!["1.1"],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_eq!(
 | 
				
			||||||
            ldap_handler.do_search(&request).await,
 | 
					            ldap_handler.do_search(&request).await,
 | 
				
			||||||
            vec![
 | 
					            vec![
 | 
				
			||||||
                LdapOp::SearchResultEntry(LdapSearchResultEntry {
 | 
					                LdapOp::SearchResultEntry(LdapSearchResultEntry {
 | 
				
			||||||
                    dn: "cn=group_1,ou=groups,dc=example,dc=com".to_string(),
 | 
					                    dn: "cn=group_1,ou=groups,dc=example,dc=com".to_string(),
 | 
				
			||||||
                    attributes: vec![LdapPartialAttribute {
 | 
					                    attributes: vec![],
 | 
				
			||||||
                        atype: "cn".to_string(),
 | 
					 | 
				
			||||||
                        vals: vec!["group_1".to_string()]
 | 
					 | 
				
			||||||
                    },],
 | 
					 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                make_search_success(),
 | 
					                make_search_success(),
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user