Ldap.php 9.67 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 3/14/17
 * Time: 11:32 PM
 */

namespace qfq;

use qfq;

require_once(__DIR__ . '/KeyValueStringParser.php');
14
require_once(__DIR__ . '/OnArray.php');
15
16
17
require_once(__DIR__ . '/../exceptions/ErrorHandler.php');
require_once(__DIR__ . '/../exceptions/UserFormException.php');

18
19
20

class Ldap {

21
22
23
24
25
26
27
28
29
    /**
     *
     */
    public function  __construct() {
        // This handler is necessary to catch 'ldap_bind()' errors.
        set_error_handler("\\qfq\\ErrorHandler::exception_error_handler");

    }

30
    /**
Carsten  Rose's avatar
Carsten Rose committed
31
32
     * @param $ldapServer
     * @return resource
33
34
     * @throws UserFormException
     */
Carsten  Rose's avatar
Carsten Rose committed
35
    private function ldapConnect($ldapServer) {
36

37
38
39
40
        $ds = ldap_connect($ldapServer);  // must be a valid LDAP server!
        if (!$ds) {
            throw new UserFormException("Unable to connect to LDAP server: $ldapServer", ERROR_LDAP_CONNECT);
        }
41
42
43
44
45

        // 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);

Carsten  Rose's avatar
Carsten Rose committed
46
47
        return $ds;
    }
48

Carsten  Rose's avatar
Carsten Rose committed
49
50
51
52
53
54
55
    /**
     * @param $ds
     * @param array $config
     * @param array $attr
     * @return resource
     */
    private function ldapSearch($ds, array $config, array $attr) {
56
57
58
59
60
        // 'Size Limit errors' are reported, even if it is not a real problem.
        // Fake all errors at the moment.
        // TODO: just drop the 'Size Limit errors' and report all others
        set_error_handler(function () { /* ignore errors */
        });
Carsten  Rose's avatar
Carsten Rose committed
61
62

        $sr = ldap_search($ds, $config[FE_LDAP_BASE_DN], $config[FE_LDAP_SEARCH], $attr, 0, $config[FE_TYPEAHEAD_LIMIT] + 1, $config[FE_LDAP_TIME_LIMIT]);
63
64
        restore_error_handler();

Carsten  Rose's avatar
Carsten Rose committed
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
        return $sr;
    }

    /**
     * @param array $config
     * @param string $searchValue
     * @param string $mode
     * @return array
     * @throws UserFormException
     */
    private function prepareConfig(array $config, $searchValue, $mode) {

        $config[FE_LDAP_ATTRIBUTES] = Support::setIfNotSet($config, FE_LDAP_ATTRIBUTES, '');
        $config[FE_LDAP_TIME_LIMIT] = Support::setIfNotSet($config, FE_LDAP_TIME_LIMIT, DEFAULT_LDAP_TIME_LIMIT);


        $config[FE_LDAP_SEARCH] = str_replace('?', $searchValue, $config[FE_LDAP_SEARCH]);

        $config[FE_TYPEAHEAD_LIMIT] = ($mode == MODE_LDAP_MULTI) ? $config[FE_TYPEAHEAD_LIMIT] : 1;

        if ($mode == MODE_LDAP_MULTI) {
            $config[FE_TYPEAHEAD_LDAP_KEY_PRINTF] = Support::setIfNotSet($config, FE_TYPEAHEAD_LDAP_KEY_PRINTF, '');
            $config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF] = Support::setIfNotSet($config, FE_TYPEAHEAD_LDAP_VALUE_PRINTF, '');
88

Carsten  Rose's avatar
Carsten Rose committed
89
90
91
            if ($config[FE_TYPEAHEAD_LDAP_KEY_PRINTF] == '') {
                $config[FE_TYPEAHEAD_LDAP_KEY_PRINTF] = $config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF];
            }
92

Carsten  Rose's avatar
Carsten Rose committed
93
94
95
            if ($config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF] == '') {
                $config[FE_TYPEAHEAD_LDAP_VALUE_PRINTF] = $config[FE_TYPEAHEAD_LDAP_KEY_PRINTF];
            }
96

Carsten  Rose's avatar
Carsten Rose committed
97
98
            if ($mode == MODE_LDAP_MULTI && $config[FE_TYPEAHEAD_LDAP_KEY_PRINTF] == '') {
                throw new UserFormException("Missing parameter '" . FE_TYPEAHEAD_LDAP_KEY_PRINTF . "' and/or '" . FE_TYPEAHEAD_LDAP_VALUE_PRINTF);
99
            }
Carsten  Rose's avatar
Carsten Rose committed
100
101
102
103
104
105
        }

        return $config;
    }

    /**
106
     *
Carsten  Rose's avatar
Carsten Rose committed
107
108
     * @param array $config [FE_LDAP_SERVER , FE_LDAP_BASE_DN, FE_LDAP_SEARCH, FE_TYPEAHEAD_LIMIT, FE_TYPEAHEAD_LDAP_KEY_PRINTF, FE_TYPEAHEAD_LDAP_VALUE_PRINTF]
     * @param string $searchValue value to search via $config[FE_LDAP_SEARCH]
109
     * @param string $mode MODE_LDAP_SINGLE | MODE_LDAP_MULTI | MODE_LDAP_PREFETCH
Carsten  Rose's avatar
Carsten Rose committed
110
111
112
113
114
     * @return array Array: [ [ 'key' => '...', 'value' => '...' ], ]
     * @throws UserFormException
     */
    public function process(array $config, $searchValue, $mode = MODE_LDAP_MULTI) {
        $arr = array();
115

Carsten  Rose's avatar
Carsten Rose committed
116
        // For TypeAhead, use an optional given F_TYPEAHEAD_LDAP_SEARCH
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
        switch ($mode) {
            case MODE_LDAP_PREFETCH:
                if (!isset($config[F_TYPEAHEAD_LDAP_SEARCH_PREFETCH]) || $config[F_TYPEAHEAD_LDAP_SEARCH_PREFETCH] == '') {
                    throw new UserFormException("Missing definition for `" . F_TYPEAHEAD_LDAP_SEARCH_PREFETCH . "`", ERROR_MISSING_TYPE_AHEAD_LDAP_SEARCH_PREFETCH);
                }
                $config[F_LDAP_SEARCH] = $config[F_TYPEAHEAD_LDAP_SEARCH_PREFETCH];
                break;

            case MODE_LDAP_MULTI:
                if (!isset($config[F_TYPEAHEAD_LDAP_SEARCH]) || $config[F_TYPEAHEAD_LDAP_SEARCH] == '') {
                    throw new UserFormException("Missing definition for `" . F_TYPEAHEAD_LDAP_SEARCH . "`", ERROR_MISSING_TYPE_AHEAD_LDAP_SEARCH);
                }
                $config[F_LDAP_SEARCH] = $config[F_TYPEAHEAD_LDAP_SEARCH];
                break;

            case MODE_LDAP_SINGLE:
                if (!isset($config[F_LDAP_SEARCH]) || $config[F_LDAP_SEARCH] == '') {
                    throw new UserFormException("Missing definition for `" . F_LDAP_SEARCH . "`", ERROR_MISSING_TYPE_AHEAD_LDAP_SEARCH);
                }
                break;

            default:
                throw new UserFormException("Unknown mode: " . $mode, ERROR_UNKNOWN_MODE);
140
141
        }

Carsten  Rose's avatar
Carsten Rose committed
142
143
144
145
146
        $searchValue = Support::ldap_escape($searchValue, null, LDAP_ESCAPE_FILTER);
        $config = $this->prepareConfig($config, $searchValue, $mode);

        $ds = $this->ldapConnect($config[FE_LDAP_SERVER]);  // must be a valid LDAP server!

147
148
149
150
151
152
        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);
            }
        }

Carsten  Rose's avatar
Carsten Rose committed
153
154
155
156
157
158
159
160
161
162
163
        $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])));

        // merge, trim, toLower, unique, values
        $attr = array_values(
            array_unique(
                OnArray::removeEmptyElementsFromArray(
                    array_merge($keyArr, $valueArr, $specificArr))));

        $sr = $this->ldapSearch($ds, $config, $attr);
164
165
        if ($sr !== false) {
            $info = ldap_get_entries($ds, $sr);
Carsten  Rose's avatar
Carsten Rose committed
166

167
            if ($mode == MODE_LDAP_MULTI) {
Carsten  Rose's avatar
Carsten Rose committed
168

169
170
                // Iterate over all Elements, per element collect all needed attributes
                for ($i = 0; $i < $info["count"]; $i++) {
Carsten  Rose's avatar
Carsten Rose committed
171

172
173
174
                    // HTML Entities will be escaped on Client side.
                    $key = $this->printfResult($keyFormat, $keyArr, $info[$i], false);
                    $value = $this->printfResult($valueFormat, $valueArr, $info[$i], false);
Carsten  Rose's avatar
Carsten Rose committed
175

176
177
178
                    if ($key == '' || $value == '') {
                        continue; // if $key or $value is empty: skip
                    }
Carsten  Rose's avatar
Carsten Rose committed
179

180
181
182
183
184
185
186
187
                    $arr[] = [API_TYPEAHEAD_KEY => $key, API_TYPEAHEAD_VALUE => $value];
                }
            } else {
                // Collect all attributes
                foreach ($attr as $key) {
                    $value = isset($info[0][$key][0]) ? $info[0][$key][0] : '';
                    $arr[$key] = $value;
                }
Carsten  Rose's avatar
Carsten Rose committed
188
            }
189
            ldap_close($ds);
Carsten  Rose's avatar
Carsten Rose committed
190
        }
191
192
193
194
195

        return $arr;
    }

    /**
196
197
     * Very specific function to prepare the later 'printfResult()'.
     *
Carsten  Rose's avatar
Carsten Rose committed
198
199
200
201
     * @param array $config Check existence and take element $key
     * @param string $key FE_TYPEAHEAD_LDAP_KEY_PRINTF, FE_TYPEAHEAD_LDAP_VALUE_PRINTF
     * @param string $fmtFirst Returns the first part of $fmtComplete - the printf format string without any args.
     * @return array            Array with all requested keynames from $fmtComplete
202
203
204
     * @throws CodeException
     * @throws UserFormException
     */
Carsten  Rose's avatar
Carsten Rose committed
205
    private function preparePrintf(array $config, $key, &$fmtFirst) {
206

Carsten  Rose's avatar
Carsten Rose committed
207
208
209
210
211
212
213
        $fmtFirst = '';

        if (!isset($config[$key])) {
            return array();
        }

        $fmtComplete = $config[$key];
214
215
216
217
218
219
220
221
222
223
224
225
        // Typical $fmtComplete: "'%s / %s / %s', cn, mail. telephonenumber"
        $arr = KeyValueStringParser::explodeWrapped(',', $fmtComplete);

        if (count($arr) < 2) {
            throw new UserFormException("Expect a sprintf compatible format string with a least one argument. Got: '" . $fmtComplete . "'", ERROR_MISSING_PRINTF_ARGUMENTS);
        }

        // unquote and return the part printf-'formatString'
        $fmtFirst = trim($arr[0], SINGLE_TICK . DOUBLE_TICK);

        array_shift($arr); // remove first entry:

Carsten  Rose's avatar
Carsten Rose committed
226
227
        // toLower & trim are mandatory here: access to LDAP entries are comming soon.
        return OnArray::arrayValueToLower(OnArray::trimArray($arr));
228
229
230
231
232
    }

    /**
     * Plays sprintf with supplied arguments. Collect the values of the arguments in the array
     *  $keyArr to pass them via 'call_user_func_array' to sprintf.
233
234
235
236
237
238
239
     *
     * @param $format
     * @param $infoElement
     * @return string       output of sprintf
     * @throws CodeException
     * @throws UserFormException
     */
240
    private function printfResult($format, array $keyArr, $infoElement, $doHtmlEntity = true) {
241

242
        $args = array($format);
243

244
        foreach ($keyArr as $key) {
245
246
247
248
249
250
251
252
253
            $val = '';

            if (isset($infoElement[$key][0])) {
                $val = $infoElement[$key][0];
                if ($doHtmlEntity === true) {
                    $val = htmlentities($val);
                }
            }
            $args[] = $val;
254
        }
255
256
257
258

        return call_user_func_array('sprintf', $args);
    }
}