From 8cb94e926381a654a5bf048e7045b8fa73d5202e Mon Sep 17 00:00:00 2001
From: Carsten  Rose <carsten.rose@math.uzh.ch>
Date: Tue, 28 Mar 2017 21:16:13 +0200
Subject: [PATCH] #3456 / LDAP: with Credentials to access 'webpass'
 Manual.rst: Updated doc for a) config.qfq.ini: LDAP_1_RDN, LDAP_1_PASSWORD,
 b) Form.parameter|FormElement.parameter: ldapUseBindCredentials
 ErrorHandler.php: removed details - the end user should not too many details.
 FormAction.php, Ldap.php, QuickFormQuery.php: implement
 'ldapUseBindCredentials' Ldap.php: set_error_handler() to catch ldap_bind()
 problems. Always set LDAP_OPT_PROTOCOL_VERSION=3 - this might cause problems
 with som LDAP Servers - we will see.

---
 extension/Documentation/Manual.rst            | 193 ++++++++++--------
 extension/config.qfq.example.ini              |   5 +-
 extension/qfq/qfq/AbstractBuildForm.php       |  13 +-
 extension/qfq/qfq/Constants.php               |   6 +
 extension/qfq/qfq/QuickFormQuery.php          |   1 +
 extension/qfq/qfq/exceptions/ErrorHandler.php |   4 +-
 extension/qfq/qfq/form/FormAction.php         |   7 +-
 extension/qfq/qfq/helper/Ldap.php             |  23 +++
 8 files changed, 158 insertions(+), 94 deletions(-)

diff --git a/extension/Documentation/Manual.rst b/extension/Documentation/Manual.rst
index fad399ca7..7be8172cd 100644
--- a/extension/Documentation/Manual.rst
+++ b/extension/Documentation/Manual.rst
@@ -166,66 +166,70 @@ Setup a *report* to manage all *forms*:
 config.qfq.ini
 --------------
 
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| Keyword                     | Example                                 | Description                                                                |
-+=============================+=========================================+============================================================================+
-| DB_USER                     | DB_USER=qfqUser                         | Credentials configured in MySQL                                            |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| DB_PASSWORD                 | DB_PASSWORD=12345678                    | Credentials configured in MySQL                                            |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| DB_SERVER                   | DB_SERVER=localhost                     | Hostname of MySQL Server                                                   |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| DB_NAME                     | DB_NAME=qfq_db                          | Database name                                                              |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| DB_NAME_TEST                | DB_NAME_TEST=qfq_db_test                | Used during development of QFQ                                             |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| DB_INIT                     | DB_INIT=set names utf8                  | Global init for using the database.                                        |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| SQL_LOG                     | SQL_LOG=sql.log                         | Filename to log SQL commands: relative to <ext_dir> or absolute.           |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| SQL_LOG_MODE                | SQL_LOG_MODE=modify                     | *all*: every statement will be logged - this is a lot                      |
-|                             |                                         | *modify*: log only statements who change data                              |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| SHOW_DEBUG_INFO             | SHOW_DEBUG_INFO=auto                    | Possible values: auto|yes|no. For 'auto': If a BE User is logged in,       |
-|                             |                                         | debug information will be shown on the fronend.                            |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| CSS_LINK_CLASS_INTERNA    L | CSS_LINK_CLASS_INTERNAL=internal        | CSS class name of links which points to internal tagets                    |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| CSS_LINK_CLASS_EXTERNAL     | CSS_LINK_CLASS_EXTERNAL=external        | CSS class name of links which points to internal tagets                    |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| CSS_CLASS_QFQ_CONTAINER     |CSS_CLASS_QFQ_CONTAINER=container        | QFQ with own Bootstrap: 'container'.                                       |
-|                             |                                         | QFQ already nested in Bootstrap of mainpage: <empty>                       |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| CSS_CLASS_QFQ_FORM_PILL     |CSS_CLASS_QFQ_FORM_PILL=qfq-color-grey-1 | Wrap around title bar for pills: CSS Class, typically a background color   |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| CSS_CLASS_QFQ_FORM_BODY     |CSS_CLASS_QFQ_FORM_BODY=qfq-color-grey-2 | Wrap around formelements: CSS Class, typically a background color          |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| DATE_FORMAT                 | DATE_FORMAT= yyyy-mm-dd                 | Possible options: yyyy-mm-dd, dd.mm.yyyy                                   |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| FORM_DATA_PATTERN_ERROR     |FORM_DATA_PATTERN_ERROR=please check pa. | Customizable error message used in validator.js. 'pattern' violation       |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| FORM_DATA_REQUIRED_ERROR    |FORM_DATA_REQUIRED_ERROR=missing value   | Customizable error message used in validator.js. 'required' fields         |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| FORM_DATA_MATCH_ERROR       |FORM_DATA_MATCH_ERROR=type error         | Customizable error message used in validator.js. 'match' retype mismatch   |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| FORM_DATA_ERROR             |FORM_DATA_ERROR=generic error            | Customizable error message used in validator.js. 'no specific' given       |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| FORM_BS_COLUMNS             | FORM_BS_COLUMNS=12                      | The whole form will be wrapped in 'col-md-??'. Default is 12 for 100%      |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| FORM_BS_LABEL_COLUMNS       | FORM_BS_LABEL_COLUMNS = 3               | Default number of BS columns for the 'label'-column                        |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| FORM_BS_INPUT_COLUMNS       | FORM_BS_INPUT_COLUMNS = 6               | Default number of BS columns for the 'input'-column                        |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| FORM_BS_NOTE_COLUMNS        | FORM_BS_NOTE_COLUMNS = 3                | Default number of BS columns for the 'note'-column                         |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| Keyword                     | Example                                         | Description                                                                |
++=============================+=================================================+============================================================================+
+| DB_USER                     | DB_USER=qfqUser                                 | Credentials configured in MySQL                                            |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| DB_PASSWORD                 | DB_PASSWORD=12345678                            | Credentials configured in MySQL                                            |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| DB_SERVER                   | DB_SERVER=localhost                             | Hostname of MySQL Server                                                   |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| DB_NAME                     | DB_NAME=qfq_db                                  | Database name                                                              |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| DB_NAME_TEST                | DB_NAME_TEST=qfq_db_test                        | Used during development of QFQ                                             |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| DB_INIT                     | DB_INIT=set names utf8                          | Global init for using the database.                                        |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| SQL_LOG                     | SQL_LOG=sql.log                                 | Filename to log SQL commands: relative to <ext_dir> or absolute.           |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| SQL_LOG_MODE                | SQL_LOG_MODE=modify                             | *all*: every statement will be logged - this is a lot                      |
+|                             |                                                 | *modify*: log only statements who change data                              |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| SHOW_DEBUG_INFO             | SHOW_DEBUG_INFO=auto                            | Possible values: auto|yes|no. For 'auto': If a BE User is logged in,       |
+|                             |                                                 | debug information will be shown on the fronend.                            |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| CSS_LINK_CLASS_INTERNA    L | CSS_LINK_CLASS_INTERNAL=internal                | CSS class name of links which points to internal tagets                    |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| CSS_LINK_CLASS_EXTERNAL     | CSS_LINK_CLASS_EXTERNAL=external                | CSS class name of links which points to internal tagets                    |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| CSS_CLASS_QFQ_CONTAINER     |CSS_CLASS_QFQ_CONTAINER=container                | QFQ with own Bootstrap: 'container'.                                       |
+|                             |                                                 | QFQ already nested in Bootstrap of mainpage: <empty>                       |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| CSS_CLASS_QFQ_FORM_PILL     |CSS_CLASS_QFQ_FORM_PILL=qfq-color-grey-1         | Wrap around title bar for pills: CSS Class, typically a background color   |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| CSS_CLASS_QFQ_FORM_BODY     |CSS_CLASS_QFQ_FORM_BODY=qfq-color-grey-2         | Wrap around formelements: CSS Class, typically a background color          |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| DATE_FORMAT                 | DATE_FORMAT= yyyy-mm-dd                         | Possible options: yyyy-mm-dd, dd.mm.yyyy                                   |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| FORM_DATA_PATTERN_ERROR     |FORM_DATA_PATTERN_ERROR=please check pa.         | Customizable error message used in validator.js. 'pattern' violation       |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| FORM_DATA_REQUIRED_ERROR    |FORM_DATA_REQUIRED_ERROR=missing value           | Customizable error message used in validator.js. 'required' fields         |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| FORM_DATA_MATCH_ERROR       |FORM_DATA_MATCH_ERROR=type error                 | Customizable error message used in validator.js. 'match' retype mismatch   |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| FORM_DATA_ERROR             |FORM_DATA_ERROR=generic error                    | Customizable error message used in validator.js. 'no specific' given       |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| FORM_BS_COLUMNS             | FORM_BS_COLUMNS=12                              | The whole form will be wrapped in 'col-md-??'. Default is 12 for 100%      |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| FORM_BS_LABEL_COLUMNS       | FORM_BS_LABEL_COLUMNS = 3                       | Default number of BS columns for the 'label'-column                        |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| FORM_BS_INPUT_COLUMNS       | FORM_BS_INPUT_COLUMNS = 6                       | Default number of BS columns for the 'input'-column                        |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| FORM_BS_NOTE_COLUMNS        | FORM_BS_NOTE_COLUMNS = 3                        | Default number of BS columns for the 'note'-column                         |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
 | FORM_BUTTON_ON_CHANGE_CLASS | FORM_BUTTON_ON_CHANGE_CLASS=alert-info btn-info | Color for save button after modification                           |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| BASE_URL_PRINT              | BASE_URL_PRINT=http://example.com       | URL where wkhtmltopdf will fetch the HTML (no parameter, those comes later)|
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| WKHTMLTOPDF                 | WKHTMLTOPDF=/usr/bin/wkhtmltopdf        | Binary where to find wkhtmltopdf                                           |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
-| EDIT_FORM_PAGE              | EDIT_FORM_PAGE = form                   | T3 Pagealias to edit a form.                                               |
-+-----------------------------+-----------------------------------------+----------------------------------------------------------------------------+
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| BASE_URL_PRINT              | BASE_URL_PRINT=http://example.com               | URL where wkhtmltopdf will fetch the HTML (no parameter, those comes later)|
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| WKHTMLTOPDF                 | WKHTMLTOPDF=/usr/bin/wkhtmltopdf                | Binary where to find wkhtmltopdf.                                          |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| EDIT_FORM_PAGE              | EDIT_FORM_PAGE = form                           | T3 Pagealias to edit a form.                                               |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
+| LDAP_1_RDN                  | LDAP_1_RDN='ou=Admin,ou=example,dc=com'         | Credentials for non-anonymous LDAP access                                  |
++-----------------------------+-------------------------------------------------+                                                                            |
+| LDAP_1_PASSWORD             | LDAP_1_PASSWORD=mySecurePassword                |                                                                            |
++-----------------------------+-------------------------------------------------+----------------------------------------------------------------------------+
 
 Example: *typo3conf/config.qfq.ini*
 
@@ -257,6 +261,8 @@ Example: *typo3conf/config.qfq.ini*
 	BASE_URL_PRINT=http://example.com
 	WKHTMLTOPDF=/usr/bin/wkhtmltopdf
 	;EDIT_FORM_PAGE = form
+	;LDAP_1_RDN='ou=Admin,dc=example,dc=com'
+	;LDAP_1_PASSWORD=mySecurePassword
 
 .. _local-documentation:
 
@@ -807,43 +813,48 @@ LDAP
 
 A form can retrieve values from an LDAP server to display or to save them. Configuration options for LDAP will be specified
 in the *parameter* field of the *Form* and/or the *FormElement*. Definitions of the *FormElement* will overwrite definitions
-of the *Form*. If LDAP access is:
+of the *Form*.
+
+Best practice for configuration - if LDAP access is:
 
 * only necessary in one *FormElement*, most usefull setup is to specify all values in that specific *FormElement*,
 * needed on multiple *FormElement*s (of the same *Form*, e.g. one *input* with *typeAhead*, one *note* and one *action*), it's more
   efficient to specify the base parameter *ldapServer*, *ldapBaseDn* in *Form.parameter* and the rest on the current
   *FormElement*.
 
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| Parameter                | Example                          | Description                                                | Form | FormElement | Used for |
-+==========================+==================================+============================================================+======+=============+==========+
-| ldapServer               | directory.example.com            | Hostname                                                   | x    | x           | TA, FSL  |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| ldapBaseDn               | ou=Addressbook,dc=example,dc=com | Base DN to start the search                                | x    | x           | TA, FSL  |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| ldapAttributes           | cn, email                        | List of attributes to save in STORE_LDAP                   | x    | x           | FSL      |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| ldapSearch               | (mail=john.doe@example.com)      | Regular LDAP search expresssion                            | x    | x           | FSL      |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| ldapTimeLimit            | 3 (default)                      | Maximum time to wait for an answer of the LDAP Server      | x    | x           | TA, FSL  |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| typeAheadLdap            | -                                | Enable LDAP as 'Typeahead' data source                     |      | x           | TA       |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| typeAheadLdapSearch      | `(|(cn=*?*)(mail=*?*))`          | Regular LDAP search expresssion                            | x    | x           | TA       |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| typeAheadLdapValuePrintf | `'%s / %s', cn, email`           | Custom format to display attributes, as `value`            | x    | x           | TA       |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| typeAheadLdapIdPrintf    | `'%s', email`                    | Custom format to display attributes, as `id`               | x    | x           | TA       |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| typeAheadLimit           | 20 (default)                     | Result will be limited to this number of entries           | x    | x           | TA       |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| typeAheadMinLength       | 2 (default)                      | Minimum number of characters before starting the search    | x    | x           | TA       |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-| fillStoreLdap            | -                                | Activate `Fill STORE LDAP` with the first retrieved record |      | x           | FSL      |
-+--------------------------+----------------------------------+------------------------------------------------------------+------+-------------+----------+
-
-* At the moment only anonymous access is supported.
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| Parameter                | Example                          | Description                                                   | Form | FormElement | Used for |
++==========================+==================================+===============================================================+======+=============+==========+
+| ldapServer               | directory.example.com            | Hostname. For LDAPS: `ldaps://directory.example.com:636`      | x    | x           | TA, FSL  |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| ldapBaseDn               | ou=Addressbook,dc=example,dc=com | Base DN to start the search                                   | x    | x           | TA, FSL  |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| ldapAttributes           | cn, email                        | List of attributes to save in STORE_LDAP                      | x    | x           | FSL      |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| ldapSearch               | (mail=john.doe@example.com)      | Regular LDAP search expresssion                               | x    | x           | FSL      |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| ldapTimeLimit            | 3 (default)                      | Maximum time to wait for an answer of the LDAP Server         | x    | x           | TA, FSL  |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| ldapUseBindCredentials   | ldapUseBindCredentials=1         | Use LDAP_1_* crendentials from config.qfq.ini for ldap_bind() | x    | x           | TA, FSL  |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| typeAheadLdap            | -                                | Enable LDAP as 'Typeahead' data source                        |      | x           | TA       |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| typeAheadLdapSearch      | `(|(cn=*?*)(mail=*?*))`          | Regular LDAP search expresssion                               | x    | x           | TA       |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| typeAheadLdapValuePrintf | `'%s / %s', cn, email`           | Custom format to display attributes, as `value`               | x    | x           | TA       |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| typeAheadLdapIdPrintf    | `'%s', email`                    | Custom format to display attributes, as `id`                  | x    | x           | TA       |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| typeAheadLimit           | 20 (default)                     | Result will be limited to this number of entries              | x    | x           | TA       |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| typeAheadMinLength       | 2 (default)                      | Minimum number of characters before starting the search       | x    | x           | TA       |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+| fillStoreLdap            | -                                | Activate `Fill STORE LDAP` with the first retrieved record    |      | x           | FSL      |
++--------------------------+----------------------------------+---------------------------------------------------------------+------+-------------+----------+
+
 * *typeAheadLimit*: there might be a hard limit on the server side (e.g. 100) - which can't be extended.
+* *ldapUseBindCredentials* is only necessary if `anonymous` access is not possible. RDN and password has to be configured in
+  `config.qfq.ini`.
 
 .. _LDAP_Typeahead:
 
@@ -863,6 +874,7 @@ The *FormElement.parameter*=*typeAheadLdap* will trigger LDAP searches on every
   * *typeAheadLdapSearch* = `(|(cn=*?*)(mail=*?*))`
   * *typeAheadLdapValuePrintf* = `'%s / %s', cn, email`
   * *typeAheadLdapIdPrintf* = `'%s', email`
+  * Optional: *ldapUseBindCredentials*=1
 
 All fetched LDAP values will be formatted with:
 * *typeAheadLdapValuePrintf*, shown to the user in a drop-down box and
@@ -876,7 +888,7 @@ To examine all possible values of an LDAP server, use the commandline tool `ldap
 
 All occurences of a '?' in *ldapSearch* will be replaced by the user data typed in via the text-*FormElement*.
 The typed data will be escaped to fullfill LDAP search limitations.
-Regular *Form* variables might be used on all parameter and will be evaluated during form load - *not* at the time when
+Regular *Form* variables might be used on all parameter and will be evaluated during form load (!) - *not* at the time when
 the user types something.
 
 .. _Fill_LDAP_STORE:
@@ -908,6 +920,7 @@ Important: LDAP access might slow down the *Form* processing on load, update or
   * *typeAheadLdapSearch* = `(|(cn=*?*)(mail=*?*))`
   * *ldapAttributes* = `givenName, sn, telephoneNumber, email`
   * *ldapSearch* = `(mail={{email::l}})`
+  * Optional: *ldapUseBindCredentials*=1
 
 After filling the store, access the content via `{{<attributename>:allbut:L:s}}`.
 
diff --git a/extension/config.qfq.example.ini b/extension/config.qfq.example.ini
index 66eaa11a8..0fc314e9e 100644
--- a/extension/config.qfq.example.ini
+++ b/extension/config.qfq.example.ini
@@ -49,4 +49,7 @@ DATE_FORMAT = yyyy-mm-dd
 BASE_URL_PRINT = http://example.com/
 WKHTMLTOPDF = /opt/wkhtmltox/bin/wkhtmltopdf
 
-;EDIT_FORM_PAGE = form
\ No newline at end of file
+;EDIT_FORM_PAGE = form
+
+;LDAP_1_RDN =
+;LDAP_1_PASSWORD =
diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php
index cacbe4743..1d4c4f29b 100644
--- a/extension/qfq/qfq/AbstractBuildForm.php
+++ b/extension/qfq/qfq/AbstractBuildForm.php
@@ -511,7 +511,7 @@ abstract class AbstractBuildForm {
 
         if (isset($formElement[FE_FILL_STORE_LDAP]) || isset($formElement[FE_TYPEAHEAD_LDAP])) {
             $keyNames = [F_LDAP_SERVER, F_LDAP_BASE_DN, F_LDAP_ATTRIBUTES, F_LDAP_SEARCH, F_TYPEAHEAD_LDAP_SEARCH, F_TYPEAHEAD_LIMIT,
-                F_TYPEAHEAD_MINLENGTH, F_TYPEAHEAD_LDAP_VALUE_PRINTF, F_TYPEAHEAD_LDAP_ID_PRINTF, F_LDAP_TIME_LIMIT];
+                F_TYPEAHEAD_MINLENGTH, F_TYPEAHEAD_LDAP_VALUE_PRINTF, F_TYPEAHEAD_LDAP_ID_PRINTF, F_LDAP_TIME_LIMIT, F_LDAP_USE_BIND_CREDENTIALS];
             $formElement = OnArray::copyArrayItemsIfNotAlreadyExist($this->formSpec, $formElement, $keyNames);
         } else {
             return $formElement; // nothing to do.
@@ -523,6 +523,11 @@ abstract class AbstractBuildForm {
             $config = OnArray::getArrayItems($formElement, [FE_LDAP_SERVER, FE_LDAP_BASE_DN, FE_LDAP_SEARCH, FE_LDAP_ATTRIBUTES]);
             $config = $this->evaluate->parseArray($config);
 
+            if($formElement[FE_LDAP_USE_BIND_CREDENTIALS]==1) {
+                $config[SYSTEM_LDAP_1_RDN] = $this->store->getVar(SYSTEM_LDAP_1_RDN, STORE_SYSTEM);
+                $config[SYSTEM_LDAP_1_PASSWORD] = $this->store->getVar(SYSTEM_LDAP_1_PASSWORD, STORE_SYSTEM);
+            }
+
             $ldap = new Ldap();
             $arr = $ldap->process($config, '', MODE_LDAP_SINGLE);
             $this->store->setStore($arr, STORE_LDAP, true);
@@ -900,6 +905,7 @@ abstract class AbstractBuildForm {
             $formElement[FE_TYPEAHEAD_LDAP_SEARCH] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_SEARCH);
             $formElement[FE_TYPEAHEAD_LDAP_VALUE_PRINTF] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_VALUE_PRINTF);
             $formElement[FE_TYPEAHEAD_LDAP_KEY_PRINTF] = Support::setIfNotSet($formElement, FE_TYPEAHEAD_LDAP_KEY_PRINTF);
+            $formElement[FE_LDAP_USE_BIND_CREDENTIALS] = Support::setIfNotSet($formElement, FE_LDAP_USE_BIND_CREDENTIALS);
 
             foreach ([FE_LDAP_SERVER, FE_LDAP_BASE_DN, FE_TYPEAHEAD_LDAP_SEARCH] as $key) {
                 if ($formElement[$key] == '') {
@@ -920,6 +926,11 @@ abstract class AbstractBuildForm {
                 FE_TYPEAHEAD_LIMIT => $formElement[FE_TYPEAHEAD_LIMIT],
             ];
 
+            if($formElement[FE_LDAP_USE_BIND_CREDENTIALS]=='1') {
+                $arr[SYSTEM_LDAP_1_RDN] = $this->store->getVar(SYSTEM_LDAP_1_RDN, STORE_SYSTEM);
+                $arr[SYSTEM_LDAP_1_PASSWORD] = $this->store->getVar(SYSTEM_LDAP_1_PASSWORD, STORE_SYSTEM);
+            }
+
             $urlParam = OnArray::toString($arr);
 
         }
diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php
index e0def040b..3f12d1a09 100644
--- a/extension/qfq/qfq/Constants.php
+++ b/extension/qfq/qfq/Constants.php
@@ -207,6 +207,7 @@ const ERROR_NO_TARGET_PATH_FILE_NAME = 1503;
 
 const ERROR_LDAP_CONNECT = 1600;
 const ERROR_MISSING_TYPE_AHEAD_LDAP_SEARCH = 1601;
+const ERROR_LDAP_BIND = 1602;
 
 // KeyValueParser
 const ERROR_KVP_VALUE_HAS_NO_KEY = 1900;
@@ -358,6 +359,9 @@ const SYSTEM_REPORT_COLUMN_NAME = 'reportColumnName'; // Keyname of SQL-column p
 const SYSTEM_REPORT_COLUMN_VALUE = 'reportColumnValue'; // Keyname of SQL-column processed at the moment.
 const SYSTEM_REPORT_FULL_LEVEL = 'reportFullLevel'; // Keyname of SQL-column processed at the moment.
 
+const SYSTEM_LDAP_1_RDN = 'LDAP_1_RDN'; // Credentials to access LDAP
+const SYSTEM_LDAP_1_PASSWORD = 'LDAP_1_PASSWORD'; // Credentials to access LDAP
+
 // die folgenden Elemente sind vermutlich nicht noetig, wenn Store Klassen gloable Vars benutzt.
 //const SYSTEM_FORM_DEF = 'formDefinition'; // Type: SANITIZE_ALNUMX / AssocArray. Final form to process. Useful for error reporting.
 //const SYSTEM_FORM_ELEMENT_DEF = 'formElementDefinition'; // Type: SANITIZE_ALL / AssocArray. Formelement which are processed at the moment. Useful for error reporting.
@@ -564,6 +568,7 @@ const F_LDAP_BASE_DN = 'ldapBaseDn';
 const F_LDAP_SEARCH = 'ldapSearch';
 const F_LDAP_ATTRIBUTES = 'ldapAttributes';
 const F_LDAP_TIME_LIMIT = 'ldapTimeLimit';
+const F_LDAP_USE_BIND_CREDENTIALS = 'ldapUseBindCredentials';
 const F_TYPEAHEAD_LIMIT = 'typeAheadLimit';
 const F_TYPEAHEAD_MINLENGTH = 'typeAheadMinLength';
 const F_TYPEAHEAD_LDAP_VALUE_PRINTF = 'typeAheadLdapValuePrintf';
@@ -655,6 +660,7 @@ const FE_LDAP_BASE_DN = F_LDAP_BASE_DN;
 const FE_LDAP_SEARCH = F_LDAP_SEARCH;
 const FE_LDAP_ATTRIBUTES = F_LDAP_ATTRIBUTES;
 const FE_LDAP_TIME_LIMIT = F_LDAP_TIME_LIMIT;
+const FE_LDAP_USE_BIND_CREDENTIALS = F_LDAP_USE_BIND_CREDENTIALS;
 const FE_TYPEAHEAD_LIMIT = F_TYPEAHEAD_LIMIT;
 const FE_TYPEAHEAD_MINLENGTH = F_TYPEAHEAD_MINLENGTH;
 const FE_TYPEAHEAD_SQL = 'typeAheadSql';
diff --git a/extension/qfq/qfq/QuickFormQuery.php b/extension/qfq/qfq/QuickFormQuery.php
index c2b8e602c..9219261ee 100644
--- a/extension/qfq/qfq/QuickFormQuery.php
+++ b/extension/qfq/qfq/QuickFormQuery.php
@@ -580,6 +580,7 @@ class QuickFormQuery {
         Support::setIfNotSet($config, F_EXTRA_DELETE_FORM, '');
         Support::setIfNotSet($config, F_SUBMIT_BUTTON_TEXT, '');
         Support::setIfNotSet($config, F_BUTTON_ON_CHANGE_CLASS, '');
+        Support::setIfNotSet($config, F_LDAP_USE_BIND_CREDENTIALS, '');
 
         return $config;
     }
diff --git a/extension/qfq/qfq/exceptions/ErrorHandler.php b/extension/qfq/qfq/exceptions/ErrorHandler.php
index 409e22b26..e2965f6ed 100644
--- a/extension/qfq/qfq/exceptions/ErrorHandler.php
+++ b/extension/qfq/qfq/exceptions/ErrorHandler.php
@@ -18,7 +18,9 @@ class ErrorHandler {
             // This error code is not included in error_reporting
             return false;
         }
-        throw new CodeException(": Catchable Error in '$file' on line $line: " . $message . " - CWD: " . getcwd(), $severity, NULL);
+//        throw new CodeException(": Catchable Error in '$file' on line $line: " . $message . " - CWD: " . getcwd(), $severity, NULL);
+        // Do not show too much to the user. E.g. 'ldap_bind()' might have problems, but the user should not see the file and linenumber.
+        throw new CodeException($message, $severity, NULL);
     }
 
 }
\ No newline at end of file
diff --git a/extension/qfq/qfq/form/FormAction.php b/extension/qfq/qfq/form/FormAction.php
index 4a6742fd2..3233b57d1 100644
--- a/extension/qfq/qfq/form/FormAction.php
+++ b/extension/qfq/qfq/form/FormAction.php
@@ -121,9 +121,14 @@ class FormAction {
                 $fe = OnArray::copyArrayItemsIfNotAlreadyExist($this->formSpec, $fe, $keyNames);
 
                 // Extract necessary elements
-                $config = OnArray::getArrayItems($fe, [FE_LDAP_SERVER, FE_LDAP_BASE_DN, FE_LDAP_SEARCH, FE_LDAP_ATTRIBUTES]);
+                $config = OnArray::getArrayItems($fe, [FE_LDAP_SERVER, FE_LDAP_BASE_DN, FE_LDAP_SEARCH, FE_LDAP_ATTRIBUTES, FE_LDAP_USE_BIND_CREDENTIALS]);
                 $config = $this->evaluate->parseArray($config);
 
+                if($fe[FE_LDAP_USE_BIND_CREDENTIALS]==1) {
+                    $config[SYSTEM_LDAP_1_RDN] = $this->store->getVar(SYSTEM_LDAP_1_RDN, STORE_SYSTEM);
+                    $config[SYSTEM_LDAP_1_PASSWORD] = $this->store->getVar(SYSTEM_LDAP_1_PASSWORD, STORE_SYSTEM);
+                }
+
                 $ldap = new Ldap();
                 $arr = $ldap->process($config, '', MODE_LDAP_SINGLE);
                 $this->store->setStore($arr, STORE_LDAP, true);
diff --git a/extension/qfq/qfq/helper/Ldap.php b/extension/qfq/qfq/helper/Ldap.php
index c966fd224..d0e5e3729 100644
--- a/extension/qfq/qfq/helper/Ldap.php
+++ b/extension/qfq/qfq/helper/Ldap.php
@@ -12,9 +12,21 @@ use qfq;
 
 require_once(__DIR__ . '/KeyValueStringParser.php');
 require_once(__DIR__ . '/OnArray.php');
+require_once(__DIR__ . '/../exceptions/ErrorHandler.php');
+require_once(__DIR__ . '/../exceptions/UserFormException.php');
+
 
 class Ldap {
 
+    /**
+     *
+     */
+    public function  __construct() {
+        // This handler is necessary to catch 'ldap_bind()' errors.
+        set_error_handler("\\qfq\\ErrorHandler::exception_error_handler");
+
+    }
+
     /**
      * @param $ldapServer
      * @return resource
@@ -26,6 +38,11 @@ class Ldap {
         if (!$ds) {
             throw new UserFormException("Unable to connect to LDAP server: $ldapServer", ERROR_LDAP_CONNECT);
         }
+
+        // http://php.net/manual/en/function.ldap-set-option.php >> This function is only available when using OpenLDAP 2.x.x OR Netscape Directory SDK x.x.
+        // Do not check for success.
+        ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
+
         return $ds;
     }
 
@@ -113,6 +130,12 @@ class Ldap {
 
         $ds = $this->ldapConnect($config[FE_LDAP_SERVER]);  // must be a valid LDAP server!
 
+        if (isset($config[SYSTEM_LDAP_1_RDN]) && isset($config[SYSTEM_LDAP_1_PASSWORD])) {
+            if (false === ldap_bind($ds, $config[SYSTEM_LDAP_1_RDN], $config[SYSTEM_LDAP_1_PASSWORD])) {
+                throw new UserFormException("LDAP: Error trying to bind: " . ldap_error($ds), ERROR_LDAP_BIND);
+            }
+        }
+
         $keyArr = $this->preparePrintf($config, FE_TYPEAHEAD_LDAP_KEY_PRINTF, $keyFormat);
         $valueArr = $this->preparePrintf($config, FE_TYPEAHEAD_LDAP_VALUE_PRINTF, $valueFormat);
         $specificArr = OnArray::arrayValueToLower(OnArray::trimArray(explode(',', $config[FE_LDAP_ATTRIBUTES])));
-- 
GitLab