# LDAP connector

The LDAP connector can be used with any directory that supports the LDAP and LDAPS protocols such as OpenLDAP, Active Directory, or eDirectory.

## Installing the connector

The steps below describe how to install and start the connector. Depending on whether you are using Kubernetes, Swarm, or RPM, the instructions will vary.

### RPM File

First, download the JAR file into `/usr/local/openiam/connectors/bin` and change the owner to the OpenIAM user.

{% code overflow="wrap" %}

```bash
wget https://download.openiam.com/release/enterprise/4.2.1.4/connectors/ldap-connector-rabbitmq.jar
chown openiam:openiam /usr/local/openiam/connectors/bin/ldap-connector-rabbitmq.jar
```

{% endcode %}

Next, create a new `.sh` file for starting LDAP connector by running:

```bash
nano ldap_start.sh
```

Content of the file:

{% code overflow="wrap" expandable="true" %}

```bash
#!/bin/bash
. /usr/local/openiam/env.conf
export VAULT_CERTS="$HOME_DIR/vault/certs/"
export JAVA_HOME="$HOME_DIR/jdk"
export JAR_FILE="ldap-connector-rabbitmq"
export JAVA_OPTS="-Xmx256m -Djdk.tls.client.protocols=TLSv1.2"
su openiam -c "$JAVA_HOME/bin/java -Dlogging.level.root=ERROR -Dlogging.level.org.openiam=ERROR -Dconfpath=$HOME_DIR/ -Djavax.net.ssl.keyStorePassword=changeit -Djavax.net.ssl.trustStore=$HOME_DIR/conf/$JAR_FILE/ -jar $JAVA_OPTS $HOME_DIR/connectors/bin/$JAR_FILE.jar > $HOME_DIR/logs/$JAR_FILE.log&"
```

{% endcode %}

Make file executable by running:

```bash
chmod +x ldap_start.sh
```

To start the connector, use the following command:

```bash
./ldap_start.sh
```

## Synchronization

OpenIAM provides two pre-configured synchronization options for the LDAP connector:

1. **OpenLDAP User**
   * Matches users by `User ID`.
2. **OpenLDAP Group**
   * Matches groups by `name` and `cn`.

To access synchronization settings:

1. Log in to the **Web Console**.
2. Navigate to **Provisioning** > **Synchronization**.
3. Search for `OpenLDAP User` or `OpenLDAP Group`.

Both synchronization configurations come with example transformation scripts.

## Enabling attributes in LDAP connector

To enable attributes in the LDAP connector, start by logging in to the OpenIAM Web Console.

1. Navigate to **Provisioning** > **Connectors**.
2. Find the LDAP connector and click **Edit**.
3. In the left-hand menu, find the **Connector Configuration** option and click it.
4. In the window that opens, select the checkbox(es) based on your requirements. In the example below, the Base DN for Group and Search Base DN for Group checkboxes are enabled.
5. Click **Save**. Proceed with configuring the Managed System.

## Configuring managed system

OpenIAM has a built-in Managed System configuration named `OpenLDAP`, which can be found by logging in to the Web Console and going to **Provisioning** > **Managed System**. It can be used **as is**, as it is already pre-configured. If you choose to configure the managed system from scratch, you must fill in the fields manually.

To configure attributes for a Managed System, follow the steps below:

{% stepper %}
{% step %}

### Provide required details and verify Base DN fields

Provide all required details and verify that **Base DN** and **Search Base DN** are present for both Group and User. If not, add them.

Example:

* Base DN for Group: `ou=groups,dc=ldap,dc=local`
* Search Base DN for Group: `ou=groups,dc=ldap,dc=local`
  {% endstep %}

{% step %}

### Configure Synchronization

Configure Synchronization for OpenLDAP User and OpenLDAP Group as described above. Verify each configuration in **both** OpenLDAP User and OpenLDAP Group, and update the necessary details as needed.
{% endstep %}

{% step %}

### Use the group transformation script

The transformation rule to be used is `LdapGroupTransformationScript.groovy`. This script can be found using the search engine under **Administration** > **Groovy Manager**, or see the full script below.
{% endstep %}

{% step %}

### Review LDAP schema and update script

Review the LDAP schema and update the code as needed. For example, if the `objectClass` is `posixGroup`, the member information is returned in the `memberUid` attribute; otherwise, it is in the `member` attribute.
{% endstep %}

{% step %}

### Specify source attribute names

Specify the attribute names to retrieve from LDAP (such as `member`) in the `SourceAttributesNames` Groovy script.
{% endstep %}
{% endstepper %}

The full group transformation script:

{% code overflow="wrap" expandable="true" %}

```bash
package org.openiam.sync.service.impl

import org.apache.commons.collections4.CollectionUtils
import org.openiam.base.AttributeOperationEnum
import org.openiam.idm.srvc.grp.dto.Group
import org.openiam.idm.srvc.grp.dto.GroupAttribute
import org.openiam.idm.srvc.meta.domain.MetadataTypeGrouping
import org.openiam.idm.srvc.synch.dto.LineObject
import org.openiam.idm.srvc.user.dto.User
import org.openiam.provision.dto.ProvisionUser
import org.openiam.sync.service.impl.service.AbstractGroupTransformScript

class LdapGroupTransformationScript extends AbstractGroupTransformScript {

    @Override
    int execute(LineObject rowObj, Group group) {

        println "** - Group Transformation script called."
        try {
            group.setPolicyId("4000");
            def ldapMdType = getMetadataTypeByNameAndGrouping("LDAP group", MetadataTypeGrouping.GROUP_TYPE);
            if (ldapMdType)
                group.setMdTypeId(ldapMdType.getId())
            else
                group.setMdTypeId("GENERAL_GROUP")
            populateObject(rowObj, group)
        } catch (Exception ex) {
            ex.printStackTrace();
            println "** - Transformation script error."
            return -1;
        }
        println "** - Transformation script completed." + group
        return NO_DELETE
    }

    private void populateObject(LineObject rowObj, Group group) {
        def columnMap = rowObj.columnMap

        group.name = columnMap.get("cn")?.value
        group.managedSysId = config.getManagedSysId()
        group.description = columnMap.get("displayName")?.value
        if (group.getDescription() && group.getDescription().length() > 254) {
            group.setDescription(group.getDescription().substring(0, 254));
        }
        group.status = "ACTIVE"
        addGroupAttribute(group, "gidNumber", columnMap.get("gidNumber")?.value);
        addGroupAttribute(group, "dn", columnMap.get("dn")?.value);

        def obj = columnMap.get("objectClass")
        if (obj.valueList.contains("posixGroup")) {
            println "Objectclass ${obj.valueList}"
            def membersAttr = columnMap.get("memberUid")
            if (membersAttr) {
                def members = membersAttr.valueList
                if (members) {
                    addGroupMembers(group, members)
                }
            }
        } else if (obj.valueList.contains("groupOfNames")) {
            println "Objectclass ${obj.valueList}"
            def membersAttrs = columnMap.get("member")
            if (membersAttrs) {
                def members = membersAttrs.valueList
                if (members) {
                    def uids = extractUids(members)
                    if (uids) {
                        addGroupMembers(group, uids)
                    }
                }
            }

        }
    }

    List<String> extractUids(List<String> valueList) {
        return valueList.collect { entry ->
            def afterUid = entry.split("uid=")[1]
            def uid = afterUid.split(",", 2)[0]
            return uid
        }
    }

    private void addGroupMembers(Group g, List<String> users) {
        if (!isNewUser && CollectionUtils.isNotEmpty(users)) {
            for (String us : users) {
                User u = getUserByLogin(us, config.getManagedSysId())
                println "found member:" + u?.displayName
                if (u != null) {
                    if (g.getUser(u.getId()) == null) {
                        try {
                            Set<String> rights = new HashSet<>()
                            rights.add("IS_CERTIFIED")
                            u.addGroup(g, rights, null, null)
                            userManager.save(new ProvisionUser(u), null)
                        } catch (Exception e) {
                            log.warn("Can't attach user to group.")
                        }
                    }
                }
            }

        }
    }


    @Override
    void init() {}

}
```

{% endcode %}

## Connector troubleshooting tips

### SSL connection issue

Symptoms:

*org.openiam.connector.core.base.exception.ConnectorException: javax.naming.CommunicationException: simple bind failed: xxxxx.xx.xxx.xxx.xxx:636 \[Root exception is javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target] Caused by: javax.naming.CommunicationException: simple bind failed: myldap.example.com:636*

Solution:

Make sure port is open and reachable, and you can connect via telnet command

```bash
telnet myldap.example.com 636
```

If you are connecting to LDAP via secure port 636, you should import the `ca_cert` issued by the domain into the Java keystore:

{% code overflow="wrap" %}

```shellscript
keytool -noprompt -import -v -trustcacerts -alias ldap_ca$(pwgen -s 13 1) -file /path/to/ca_cert.crt -keystore /usr/local/openiam/jdk/lib/security/cacerts -keypass changeit -storepass changeit
```

{% endcode %}

{% hint style="info" %}
A certificate must be imported into the proper JVM that is used to start the connector. Afterwards, you need to restart the connector.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs-beta.openiam.com/application-onboarding/connectors/ldap-connector.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
