Completed
Pull Request — master (#6286)
by Neil
04:49
created

includes/authentication/active_directory.inc.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
use Phpass\PasswordHash;
4
5
// easier to rewrite for Active Directory than to bash it into existing LDAP implementation
6
7
// disable certificate checking before connect if required
8
if (isset($config['auth_ad_check_certificates']) &&
9
          !$config['auth_ad_check_certificates']) {
10
    putenv('LDAPTLS_REQCERT=never');
11
};
12
13
if (isset($config['auth_ad_debug']) && $config['auth_ad_debug']) {
14
    ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7);
15
}
16
17
$ldap_connection = @ldap_connect($config['auth_ad_url']);
18
19
// disable referrals and force ldap version to 3
20
21
ldap_set_option($ldap_connection, LDAP_OPT_REFERRALS, 0);
22
ldap_set_option($ldap_connection, LDAP_OPT_PROTOCOL_VERSION, 3);
23
24
// Bind to AD
25
if (!ad_bind($ldap_connection)) {
26
    d_echo(ldap_error($ldap_connection) . PHP_EOL);
27
}
28
29
function authenticate($username, $password)
30
{
31
    global $config, $ldap_connection, $auth_error;
32
33
    if ($ldap_connection) {
34
        // bind with sAMAccountName instead of full LDAP DN
35
        if ($username && $password && ldap_bind($ldap_connection, "{$username}@{$config['auth_ad_domain']}", $password)) {
36
            // group membership in one of the configured groups is required
37
            if (isset($config['auth_ad_require_groupmembership']) &&
38
                $config['auth_ad_require_groupmembership']) {
39
                $search = ldap_search(
40
                    $ldap_connection,
41
                    $config['auth_ad_base_dn'],
42
                    get_auth_ad_user_filter($username),
43
                    array('memberOf')
44
                );
45
                $entries = ldap_get_entries($ldap_connection, $search);
46
                unset($entries[0]['memberof']['count']); //remove the annoying count
47
48
                foreach ($entries[0]['memberof'] as $entry) {
49
                    $group_cn = get_cn($entry);
50
                    if (isset($config['auth_ad_groups'][$group_cn]['level'])) {
51
                        // user is in one of the defined groups
52
                        adduser($username);
53
                        return 1;
54
                    }
55
                }
56
57
                if (isset($config['auth_ad_debug']) && $config['auth_ad_debug']) {
58
                    if ($entries['count'] == 0) {
59
                        $auth_error = 'No groups found for user, check base dn';
60
                    } else {
61
                        $auth_error = 'User is not in one of the required groups';
62
                    }
63
                } else {
64
                    $auth_error = 'Invalid credentials';
65
                }
66
67
                return 0;
68
            } else {
69
                // group membership is not required and user is valid
70
                adduser($username);
71
                return 1;
72
            }
73
        }
74
    }
75
76
    if (!isset($password) || $password == '') {
77
        $auth_error = "A password is required";
78
    } elseif (isset($config['auth_ad_debug']) && $config['auth_ad_debug']) {
79
        ldap_get_option($ldap_connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extended_error);
80
        $auth_error = ldap_error($ldap_connection).'<br />'.$extended_error;
81
    } else {
82
        $auth_error = ldap_error($ldap_connection);
83
    }
84
85
    return 0;
86
}
87
88
function reauthenticate($sess_id, $token)
89
{
90
    global $ldap_connection;
91
92
    if (ad_bind($ldap_connection, false)) {
93
        $sess_id = clean($sess_id);
94
        $token = clean($token);
95
        list($username, $hash) = explode('|', $token);
96
97
        if (!user_exists($username)) {
98
            d_echo("$username is not a valid AD user\n");
99
            return 0;
100
        }
101
102
        $session = dbFetchRow(
103
            "SELECT * FROM `session` WHERE `session_username`=? AND session_value=?",
104
            array($username, $sess_id),
105
            true
106
        );
107
        $hasher = new PasswordHash(8, false);
108
        if ($hasher->CheckPassword($username . $session['session_token'], $hash)) {
109
            $_SESSION['username'] = $username;
110
            return 1;
111
        } else {
112
            d_echo("Reauthenticate token check failed\n");
113
            return 0;
114
        }
115
    }
116
117
    return 0;
118
}
119
120
121
function passwordscanchange()
122
{
123
    // not supported so return 0
124
    return 0;
125
}
126
127
128
function changepassword()
129
{
130
    // not supported so return 0
131
    return 0;
132
}
133
134
135
function auth_usermanagement()
136
{
137
    // not supported so return 0
138
    return 0;
139
}
140
141
142
function adduser($username, $level = 0, $email = '', $realname = '', $can_modify_passwd = 0, $description = '', $twofactor = 0)
0 ignored issues
show
The parameter $twofactor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
143
{
144
    // not supported so return 0
145
    return 0;
146
}
147
148
function user_exists_in_db($username)
149
{
150
    $return = dbFetchCell('SELECT COUNT(*) FROM users WHERE username = ?', array($username), true);
151
    return $return;
152
}
153
154
function user_exists($username)
155
{
156
    global $config, $ldap_connection;
157
158
    $search = ldap_search(
159
        $ldap_connection,
160
        $config['auth_ad_base_dn'],
161
        get_auth_ad_user_filter($username),
162
        array('samaccountname')
163
    );
164
    $entries = ldap_get_entries($ldap_connection, $search);
165
166
167
    if ($entries['count']) {
168
        return 1;
169
    }
170
171
    return 0;
172
}
173
174
175
function get_userlevel($username)
176
{
177
    global $config, $ldap_connection;
178
179
    $userlevel = 0;
180
    if (isset($config['auth_ad_require_groupmembership']) && $config['auth_ad_require_groupmembership'] == 0) {
181
        if (isset($config['auth_ad_global_read']) && $config['auth_ad_global_read'] === 1) {
182
            $userlevel = 5;
183
        }
184
    }
185
186
    // Find all defined groups $username is in
187
    $search = ldap_search(
188
        $ldap_connection,
189
        $config['auth_ad_base_dn'],
190
        get_auth_ad_user_filter($username),
191
        array('memberOf')
192
    );
193
    $entries = ldap_get_entries($ldap_connection, $search);
194
    unset($entries[0]['memberof']['count']);
195
196
    // Loop the list and find the highest level
197
    foreach ($entries[0]['memberof'] as $entry) {
198
        $group_cn = get_cn($entry);
199
        if (isset($config['auth_ad_groups'][$group_cn]['level']) &&
200
             $config['auth_ad_groups'][$group_cn]['level'] > $userlevel) {
201
            $userlevel = $config['auth_ad_groups'][$group_cn]['level'];
202
        }
203
    }
204
205
    return $userlevel;
206
}
207
208
209 View Code Duplication
function get_userid($username)
210
{
211
    global $config, $ldap_connection;
212
213
    $attributes = array('objectsid');
214
    $search = ldap_search(
215
        $ldap_connection,
216
        $config['auth_ad_base_dn'],
217
        get_auth_ad_user_filter($username),
218
        $attributes
219
    );
220
    $entries = ldap_get_entries($ldap_connection, $search);
221
222
    if ($entries['count']) {
223
        return get_userid_from_sid(sid_from_ldap($entries[0]['objectsid'][0]));
224
    }
225
226
    return -1;
227
}
228
229
function get_domain_sid()
230
{
231
    global $config, $ldap_connection;
232
233
    $search = ldap_read(
234
        $ldap_connection,
235
        $config['auth_ad_base_dn'],
236
        '(objectClass=*)',
237
        array('objectsid')
238
    );
239
    $entry = ldap_get_entries($ldap_connection, $search);
240
    return substr(sid_from_ldap($entry[0]['objectsid'][0]), 0, 41);
241
}
242
243
function get_user($user_id)
244
{
245
    global $config, $ldap_connection;
246
247
    $domain_sid = get_domain_sid();
248
249
    $search_filter = "(&(objectcategory=person)(objectclass=user)(objectsid=$domain_sid-$user_id))";
250
    $attributes = array('samaccountname', 'displayname', 'objectsid', 'mail');
251
    $search = ldap_search($ldap_connection, $config['auth_ad_base_dn'], $search_filter, $attributes);
252
    $entry = ldap_get_entries($ldap_connection, $search);
253
254
    if (isset($entry[0]['samaccountname'][0])) {
255
        return user_from_ad($entry[0]);
256
    }
257
258
    return array();
259
}
260
261
function deluser($userid)
262
{
263
    dbDelete('bill_perms', '`user_id` =  ?', array($userid));
264
    dbDelete('devices_perms', '`user_id` =  ?', array($userid));
265
    dbDelete('ports_perms', '`user_id` =  ?', array($userid));
266
    dbDelete('users_prefs', '`user_id` =  ?', array($userid));
267
    return 0;
268
}
269
270
271
function get_userlist()
272
{
273
    global $config, $ldap_connection;
274
    $userlist = array();
275
276
    $ldap_groups = get_group_list();
277
278
    foreach ($ldap_groups as $ldap_group) {
279
        $search_filter = "(memberOf=$ldap_group)";
280
        if ($config['auth_ad_user_filter']) {
281
            $search_filter = "(&{$config['auth_ad_user_filter']}$search_filter)";
282
        }
283
        $attributes = array('samaccountname', 'displayname', 'objectsid', 'mail');
284
        $search = ldap_search($ldap_connection, $config['auth_ad_base_dn'], $search_filter, $attributes);
285
        $results = ldap_get_entries($ldap_connection, $search);
286
287
        foreach ($results as $result) {
288
            if (isset($result['samaccountname'][0])) {
289
                $userlist[$result['samaccountname'][0]] = user_from_ad($result);
290
            }
291
        }
292
    }
293
294
    return array_values($userlist);
295
}
296
297
/**
298
 * Generate a user array from an AD LDAP entry
299
 * Must have the attributes: objectsid, samaccountname, displayname, mail
300
 * @internal
301
 *
302
 * @param $entry
303
 * @return array
304
 */
305
function user_from_ad($entry)
306
{
307
    return array(
308
        'user_id' => get_userid_from_sid(sid_from_ldap($entry['objectsid'][0])),
309
        'username' => $entry['samaccountname'][0],
310
        'realname' => $entry['displayname'][0],
311
        'email' => $entry['mail'][0],
312
        'descr' => '',
313
        'level' => get_userlevel($entry['samaccountname'][0]),
314
        'can_modify_passwd' => 0,
315
        'twofactor' => 0,
316
        // 'dashboard' => 'broken!',
317
    );
318
}
319
320
321
function can_update_users()
322
{
323
    // not supported so return 0
324
    return 0;
325
}
326
327
328
function update_user($user_id, $realname, $level, $can_modify_passwd, $email)
329
{
330
    // not supported so return 0
331
    return 0;
332
}
333
334 View Code Duplication
function get_email($username)
335
{
336
    global $config, $ldap_connection;
337
338
    $attributes = array('mail');
339
    $search = ldap_search(
340
        $ldap_connection,
341
        $config['auth_ad_base_dn'],
342
        get_auth_ad_user_filter($username),
343
        $attributes
344
    );
345
    $result = ldap_get_entries($ldap_connection, $search);
346
    unset($result[0]['mail']['count']);
347
    return current($result[0]['mail']);
348
}
349
350 View Code Duplication
function get_fullname($username)
351
{
352
    global $config, $ldap_connection;
353
354
    $attributes = array('name');
355
    $result = ldap_search(
356
        $ldap_connection,
357
        $config['auth_ad_base_dn'],
358
        get_auth_ad_user_filter($username),
359
        $attributes
360
    );
361
    $entries = ldap_get_entries($ldap_connection, $result);
362
    if ($entries['count'] > 0) {
363
        $membername = $entries[0]['name'][0];
364
    } else {
365
        $membername = $username;
366
    }
367
368
    return $membername;
369
}
370
371
372 View Code Duplication
function get_group_list()
373
{
374
    global $config;
375
376
    $ldap_groups   = array();
377
378
    // show all Active Directory Users by default
379
    $default_group = 'Users';
380
381
    if (isset($config['auth_ad_group'])) {
382
        if ($config['auth_ad_group'] !== $default_group) {
383
            $ldap_groups[] = $config['auth_ad_group'];
384
        }
385
    }
386
387
    if (!isset($config['auth_ad_groups']) && !isset($config['auth_ad_group'])) {
388
        $ldap_groups[] = get_dn($default_group);
389
    }
390
391
    foreach ($config['auth_ad_groups'] as $key => $value) {
392
        $ldap_groups[] = get_dn($key);
393
    }
394
395
    return $ldap_groups;
396
}
397
398 View Code Duplication
function get_dn($samaccountname)
399
{
400
    global $config, $ldap_connection;
401
402
403
    $attributes = array('dn');
404
    $result = ldap_search(
405
        $ldap_connection,
406
        $config['auth_ad_base_dn'],
407
        get_auth_ad_group_filter($samaccountname),
408
        $attributes
409
    );
410
    $entries = ldap_get_entries($ldap_connection, $result);
411
    if ($entries['count'] > 0) {
412
        return $entries[0]['dn'];
413
    } else {
414
        return '';
415
    }
416
}
417
418
function get_cn($dn)
419
{
420
    $dn = str_replace('\\,', '~C0mmA~', $dn);
421
    preg_match('/[^,]*/', $dn, $matches, PREG_OFFSET_CAPTURE, 3);
422
    return str_replace('~C0mmA~', ',', $matches[0][0]);
423
}
424
425
function get_userid_from_sid($sid)
426
{
427
    return preg_replace('/.*-(\d+)$/', '$1', $sid);
428
}
429
430 View Code Duplication
function sid_from_ldap($sid)
431
{
432
        $sidUnpacked = unpack('H*hex', $sid);
433
        $sidHex = array_shift($sidUnpacked);
434
        $subAuths = unpack('H2/H2/n/N/V*', $sid);
435
        $revLevel = hexdec(substr($sidHex, 0, 2));
436
        $authIdent = hexdec(substr($sidHex, 4, 12));
437
        return 'S-'.$revLevel.'-'.$authIdent.'-'.implode('-', $subAuths);
438
}
439
440
/**
441
 * Bind to AD with the bind user if available, otherwise anonymous bind
442
 * @internal
443
 *
444
 * @param resource $connection the ldap connection resource
445
 * @param bool $allow_anonymous attempt anonymous bind if bind user isn't available
446
 * @return bool success or failure
447
 */
448
function ad_bind($connection, $allow_anonymous = true)
449
{
450
    global $config;
451
452
    if (isset($config['auth_ad_binduser']) && isset($config['auth_ad_bindpassword'])) {
453
        // With specified bind user
454
        return ldap_bind(
455
            $connection,
456
            "${config['auth_ad_binduser']}@${config['auth_ad_domain']}",
457
            "${config['auth_ad_bindpassword']}"
458
        );
459
    }
460
461
    if ($allow_anonymous) {
462
        // Anonymous
463
        return ldap_bind($connection);
464
    }
465
466
    return false;
467
}
468