Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

main/auth/external_login/ldap.inc.php (1 issue)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 * This files is included by newUser.ldap.php and login.ldap.php
7
 * It implements the functions nedded by both files.
8
 * */
9
require_once __DIR__.'/../../inc/global.inc.php';
10
11
$debug = false;
12
13
/**
14
 * Returns a transcoded and trimmed string.
15
 *
16
 * @param string
17
 *
18
 * @return string
19
 *
20
 * @author ndiechburg <[email protected]>
21
 * */
22
function extldap_purify_string($string)
23
{
24
    global $extldap_config;
25
    if (isset($extldap_config['encoding'])) {
26
        return trim(api_to_system_encoding($string, $extldap_config['encoding']));
27
    } else {
28
        return trim($string);
29
    }
30
}
31
32
/**
33
 * Establishes a connection to the LDAP server and sets the protocol version.
34
 *
35
 * @return resource|bool ldap link identifier or false
36
 *
37
 * @author ndiechburg <[email protected]>
38
 * */
39
function extldap_connect()
40
{
41
    global $extldap_config, $debug;
42
43
    if (!is_array($extldap_config['host'])) {
44
        $extldap_config['host'] = [$extldap_config['host']];
45
    }
46
47
    foreach ($extldap_config['host'] as $host) {
48
        //Trying to connect
49
        if (isset($extldap_config['port'])) {
50
            $ds = ldap_connect($host, $extldap_config['port']);
51
        } else {
52
            $ds = ldap_connect($host);
53
        }
54
        if (!$ds) {
55
            $port = isset($extldap_config['port']) ? $extldap_config['port'] : 389;
56
            if ($debug) {
57
                error_log(
58
                    'EXTLDAP ERROR : cannot connect to '.$extldap_config['host'].':'.$port
59
                );
60
            }
61
        } else {
62
            break;
63
        }
64
    }
65
    if (!$ds) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ds seems to be defined by a foreach iteration on line 47. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
66
        if ($debug) {
67
            error_log('EXTLDAP ERROR : no valid server found');
68
        }
69
70
        return false;
71
    }
72
    // Setting protocol version
73
    if (isset($extldap_config['protocol_version'])) {
74
        if (!ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, $extldap_config['protocol_version'])) {
75
            ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 2);
76
        }
77
    }
78
79
    // Setting protocol version
80
    if (isset($extldap_config['referrals'])) {
81
        if (!ldap_set_option($ds, LDAP_OPT_REFERRALS, $extldap_config['referrals'])) {
82
            ldap_set_option($ds, LDAP_OPT_REFERRALS, $extldap_config['referrals']);
83
        }
84
    }
85
86
    return $ds;
87
}
88
89
/**
90
 * Authenticate user on external ldap server and return user ldap entry if that succeeds.
91
 *
92
 * @param string $password
93
 *
94
 * @return mixed false if user cannot authenticate on ldap, user ldap entry if tha succeeds
95
 *
96
 * @author ndiechburg <[email protected]>
97
 * Modified by [email protected]
98
 * Add possibility to get user info from LDAP without check password (if CAS auth and LDAP profil update)
99
 *
100
 * */
101
function extldap_authenticate($username, $password, $in_auth_with_no_password = false)
102
{
103
    global $extldap_config, $debug;
104
105
    if (empty($username) || empty($password)) {
106
        return false;
107
    }
108
109
    $ds = extldap_connect();
110
    if (!$ds) {
111
        return false;
112
    }
113
114
    // Connection as admin to search dn of user
115
    $ldapbind = @ldap_bind($ds, $extldap_config['admin_dn'], $extldap_config['admin_password']);
116
    if ($ldapbind === false) {
117
        if ($debug) {
118
            error_log(
119
                'EXTLDAP ERROR : cannot connect with admin login/password'
120
            );
121
        }
122
123
        return false;
124
    }
125
    $user_search = extldap_get_user_search_string($username);
126
    // Search distinguish name of user
127
    $sr = ldap_search($ds, $extldap_config['base_dn'], $user_search);
128
    if (!$sr) {
129
        if ($debug) {
130
            error_log(
131
                'EXTLDAP ERROR : ldap_search('.$ds.', '.$extldap_config['base_dn'].", $user_search) failed"
132
            );
133
        }
134
135
        return false;
136
    }
137
138
    $entries_count = ldap_count_entries($ds, $sr);
139
140
    if ($entries_count > 1) {
141
        if ($debug) {
142
            error_log(
143
                'EXTLDAP ERROR : more than one entry for that user ( ldap_search(ds, '.$extldap_config['base_dn'].", $user_search) )"
144
            );
145
        }
146
147
        return false;
148
    }
149
    if ($entries_count < 1) {
150
        if ($debug) {
151
            error_log(
152
                'EXTLDAP ERROR :  No entry for that user ( ldap_search(ds, '.$extldap_config['base_dn'].", $user_search) )"
153
            );
154
        }
155
156
        return false;
157
    }
158
    $users = ldap_get_entries($ds, $sr);
159
    $user = $users[0];
160
161
    // If we just want to have user info from LDAP and not to check password
162
    if ($in_auth_with_no_password) {
163
        return $user;
164
    }
165
166
    // now we try to autenthicate the user in the ldap
167
    $ubind = @ldap_bind($ds, $user['dn'], $password);
168
    if ($ubind !== false) {
169
        return $user;
170
    } else {
171
        if ($debug) {
172
            error_log('EXTLDAP : Wrong password for '.$user['dn']);
173
        }
174
175
        return false;
176
    }
177
}
178
179
/**
180
 * Return an array with userinfo compatible with chamilo using $extldap_user_correspondance
181
 * configuration array declared in ldap.conf.php file.
182
 *
183
 * @param array ldap user
184
 * @param array correspondance array (if not set use extldap_user_correspondance declared in auth.conf.php
185
 *
186
 * @return array userinfo array
187
 *
188
 * @author ndiechburg <[email protected]>
189
 * */
190
function extldap_get_chamilo_user($ldap_user, $cor = null)
191
{
192
    global $extldap_user_correspondance, $debug;
193
    if (is_null($cor)) {
194
        $cor = $extldap_user_correspondance;
195
    }
196
197
    $chamilo_user = [];
198
    foreach ($cor as $chamilo_field => $ldap_field) {
199
        if (is_array($ldap_field)) {
200
            $chamilo_user[$chamilo_field] = extldap_get_chamilo_user($ldap_user, $ldap_field);
201
            continue;
202
        }
203
204
        switch ($ldap_field) {
205
            case 'func':
206
                $func = "extldap_get_$chamilo_field";
207
                if (function_exists($func)) {
208
                    $chamilo_user[$chamilo_field] = extldap_purify_string($func($ldap_user));
209
                } else {
210
                    if ($debug) {
211
                        error_log(
212
                            "EXTLDAP WARNING : You forgot to declare $func"
213
                        );
214
                    }
215
                }
216
                break;
217
            default:
218
                //if string begins with "!", then this is a constant
219
                if ($ldap_field[0] === '!') {
220
                    $chamilo_user[$chamilo_field] = trim($ldap_field, "!\t\n\r\0");
221
                    break;
222
                }
223
                if (!array_key_exists($ldap_field, $ldap_user)) {
224
                    $lowerCaseFieldName = strtolower($ldap_field);
225
                    if (array_key_exists($lowerCaseFieldName, $ldap_user)) {
226
                        $ldap_field = $lowerCaseFieldName;
227
                    }
228
                }
229
                if (isset($ldap_user[$ldap_field][0])) {
230
                    $chamilo_user[$chamilo_field] = extldap_purify_string($ldap_user[$ldap_field][0]);
231
                } else {
232
                    if ($debug) {
233
                        error_log(
234
                            'EXTLDAP WARNING : '.$ldap_field.'[0] field is not set in ldap array'
235
                        );
236
                    }
237
                }
238
                break;
239
        }
240
    }
241
242
    return $chamilo_user;
243
}
244
245
/**
246
 * Please declare here all the function you use in extldap_user_correspondance
247
 * All these functions must have an $ldap_user parameter. This parameter is the
248
 * array returned by the ldap for the user.
249
 * */
250
function extldap_get_status($ldap_user)
251
{
252
    return STUDENT;
253
}
254
255
function extldap_get_admin($ldap_user)
256
{
257
    return false;
258
}
259
260
/**
261
 * return the string used to search a user in ldap.
262
 *
263
 * @param string username
264
 *
265
 * @return string the serach string
266
 *
267
 * @author ndiechburg <[email protected]>
268
 * */
269
function extldap_get_user_search_string($username)
270
{
271
    global $extldap_config;
272
    // init
273
    $filter = '('.$extldap_config['user_search'].')';
274
    // replacing %username% by the actual username
275
    $filter = str_replace('%username%', $username, $filter);
276
    // append a global filter if needed
277
    if (isset($extldap_config['filter']) && $extldap_config['filter'] != "") {
278
        $filter = '(&'.$filter.'('.$extldap_config['filter'].'))';
279
    }
280
281
    return $filter;
282
}
283
284
/**
285
 * Imports all LDAP users into Chamilo.
286
 *
287
 * @return false|null false on error, true otherwise
288
 */
289
function extldap_import_all_users()
290
{
291
    global $extldap_config, $debug;
292
    //echo "Connecting...\n";
293
    $ds = extldap_connect();
294
    if (!$ds) {
295
        return false;
296
    }
297
    //echo "Binding...\n";
298
    $ldapbind = false;
299
    //Connection as admin to search dn of user
300
    $ldapbind = @ldap_bind($ds, $extldap_config['admin_dn'], $extldap_config['admin_password']);
301
    if ($ldapbind === false) {
302
        if ($debug) {
303
            error_log(
304
                'EXTLDAP ERROR : cannot connect with admin login/password'
305
            );
306
        }
307
308
        return false;
309
    }
310
    //browse ASCII values from a to z to avoid 1000 results limit of LDAP
311
    $count = 0;
312
    $alphanum = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
313
    for ($a = 97; $a <= 122; $a++) {
314
        $alphanum[] = chr($a);
315
    }
316
    foreach ($alphanum as $char1) {
317
        foreach ($alphanum as $char2) {
318
            $user_search = $extldap_config['user_search_import_all_users'];
319
            //Search distinguish name of user
320
            $sr = ldap_search($ds, $extldap_config['base_dn'], $user_search);
321
            if (!$sr) {
322
                if ($debug) {
323
                    error_log(
324
                        'EXTLDAP ERROR : ldap_search('.$ds.', '.$extldap_config['base_dn'].", $user_search) failed"
325
                    );
326
                }
327
328
                return false;
329
            }
330
            //echo "Getting entries\n";
331
            $users = ldap_get_entries($ds, $sr);
332
            //echo "Entries: ".$users['count']."\n";
333
            for ($key = 0; $key < $users['count']; $key++) {
334
                $user_id = extldap_add_user_by_array($users[$key], true);
335
                $count++;
336
            }
337
        }
338
    }
339
    //echo "Found $count users in total\n";
340
    @ldap_close($ds);
341
}
342
343
/**
344
 * Insert users from an array of user fields.
345
 */
346
function extldap_add_user_by_array($data, $update_if_exists = true)
347
{
348
    global $extldap_user_correspondance;
349
350
    $lastname = api_convert_encoding($data[$extldap_user_correspondance['lastname']][0], api_get_system_encoding(), 'UTF-8');
351
    $firstname = api_convert_encoding($data[$extldap_user_correspondance['firstname']][0], api_get_system_encoding(), 'UTF-8');
352
    $email = $data[$extldap_user_correspondance['email']][0];
353
    $username = $data[$extldap_user_correspondance['username']][0];
354
355
    // TODO the password, if encrypted at the source, will be encrypted twice, which makes it useless. Try to fix that.
356
    $passwordKey = isset($extldap_user_correspondance['password']) ? $extldap_user_correspondance['password'] : 'userPassword';
357
    $password = $data[$passwordKey][0];
358
359
    // To ease management, we add the step-year (etape-annee) code
360
    //$official_code = $etape."-".$annee;
361
    $official_code = api_convert_encoding($data[$extldap_user_correspondance['official_code']][0], api_get_system_encoding(), 'UTF-8');
362
    $auth_source = 'ldap';
363
364
    // No expiration date for students (recover from LDAP's shadow expiry)
365
    $expiration_date = '';
366
    $active = 1;
367
    $status = 5;
368
    $phone = '';
369
    $picture_uri = '';
370
    // Adding user
371
    $user_id = 0;
372
    if (UserManager::is_username_available($username)) {
373
        //echo "$username\n";
374
        $user_id = UserManager::create_user(
375
            $firstname,
376
            $lastname,
377
            $status,
378
            $email,
379
            $username,
380
            $password,
381
            $official_code,
382
            api_get_setting('platformLanguage'),
383
            $phone,
384
            $picture_uri,
385
            $auth_source,
386
            $expiration_date,
387
            $active
388
        );
389
    } else {
390
        if ($update_if_exists) {
391
            $user = api_get_user_info($username);
392
            $user_id = $user['user_id'];
393
            //echo "$username\n";
394
            UserManager::update_user(
395
                $user_id,
396
                $firstname,
397
                $lastname,
398
                $username,
399
                null,
400
                null,
401
                $email,
402
                $status,
403
                $official_code,
404
                $phone,
405
                $picture_uri,
406
                $expiration_date,
407
                $active
408
            );
409
        }
410
    }
411
412
    return $user_id;
413
}
414
415
/**
416
 * Get one user's single attribute value.
417
 * User is identified by filter.
418
 * $extldap_config['filter'] is also applied in complement, if defined.
419
 *
420
 * @param $filter string LDAP entry filter, such as '(uid=10000)'
421
 * @param $attribute string name of the LDAP attribute to read the value from
422
 *
423
 * @throws Exception if more than one entries matched or on internal error
424
 *
425
 * @return string|bool the single matching user entry's single attribute value or false if not found
426
 */
427
function extldapGetUserAttributeValue($filter, $attribute)
428
{
429
    global $extldap_config;
430
431
    if (array_key_exists('filter', $extldap_config) && !empty($extldap_config['filter'])) {
432
        $filter = '(&'.$filter.'('.$extldap_config['filter'].'))';
433
    }
434
435
    $ldap = extldap_connect();
436
    if (false === $ldap) {
437
        throw new Exception(get_lang('LDAPConnectFailed'));
438
    }
439
440
    if (false === ldap_bind($ldap, $extldap_config['admin_dn'], $extldap_config['admin_password'])) {
441
        throw new Exception(get_lang('LDAPBindFailed'));
442
    }
443
444
    $searchResult = ldap_search($ldap, $extldap_config['base_dn'], $filter, [$attribute]);
445
    if (false === $searchResult) {
446
        throw new Exception(get_lang('LDAPSearchFailed'));
447
    }
448
449
    switch (ldap_count_entries($ldap, $searchResult)) {
450
        case 0:
451
            return false;
452
        case 1:
453
            $entry = ldap_first_entry($ldap, $searchResult);
454
            if (false === $entry) {
455
                throw new Exception(get_lang('LDAPFirstEntryFailed'));
456
            }
457
            $values = ldap_get_values($ldap, $entry, $attribute);
458
            if (false == $values) {
459
                throw new Exception(get_lang('LDAPGetValuesFailed'));
460
            }
461
            if ($values['count'] == 1) {
462
                return $values[0];
463
            }
464
            throw new Exception(get_lang('MoreThanOneAttributeValueFound'));
465
        default:
466
            throw new Exception(get_lang('MoreThanOneUserMatched'));
467
    }
468
}
469
470
/**
471
 * Get the username from the CAS-supplied user identifier.
472
 *
473
 * searches in attribute $extldap_user_correspondance['extra']['cas_user'] or 'uid' by default
474
 * reads value from attribute $extldap_user_correspondance['username'] or 'uid' by default
475
 *
476
 * @param $casUser string code returned from the CAS server to identify the user
477
 *
478
 * @throws Exception on error
479
 *
480
 * @return string|bool user login name, false if not found
481
 */
482
function extldapCasUserLogin($casUser)
483
{
484
    global $extldap_user_correspondance;
485
486
    // which LDAP attribute is the cas user identifier stored in ?
487
    $attributeToFilterOn = 'uid';
488
    if (is_array($extldap_user_correspondance) && array_key_exists('extra', $extldap_user_correspondance)) {
489
        $extra = $extldap_user_correspondance['extra'];
490
        if (is_array($extra) && array_key_exists('cas_user', $extra) && !empty($extra['cas_user'])) {
491
            $attributeToFilterOn = $extra['cas_user'];
492
        }
493
    }
494
495
    // which LDAP attribute is the username ?
496
    $attributeToRead = 'uid';
497
    if (is_array($extldap_user_correspondance)
498
        && array_key_exists('username', $extldap_user_correspondance)
499
        && !empty($extldap_user_correspondance['username'])
500
    ) {
501
        $attributeToRead = $extldap_user_correspondance['username'];
502
    }
503
504
    // return the value
505
    return extldapGetUserAttributeValue("($attributeToFilterOn=$casUser)", $attributeToRead);
506
}
507