Issues (2963)

LibreNMS/Authentication/ActiveDirectoryCommon.php (1 issue)

1
<?php
2
/**
3
 * ActiveDirectoryCommonirectoryCommon.php
4
 *
5
 * Common code from AD auth modules
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
 *
20
 * @link       https://www.librenms.org
21
 *
22
 * @copyright  2018 Tony Murray
23
 * @author     Tony Murray <[email protected]>
24
 */
25
26
namespace LibreNMS\Authentication;
27
28
use LibreNMS\Config;
29
30
trait ActiveDirectoryCommon
31
{
32
    protected function getUseridFromSid($sid)
33
    {
34
        return preg_replace('/.*-(\d+)$/', '$1', $sid);
35
    }
36
37
    protected function sidFromLdap($sid)
38
    {
39
        $sidUnpacked = unpack('H*hex', $sid);
40
        $sidHex = array_shift($sidUnpacked);
41
        $subAuths = unpack('H2/H2/n/N/V*', $sid);
42
        if (PHP_INT_SIZE <= 4) {
43
            for ($i = 1; $i <= count($subAuths); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
44
                if ($subAuths[$i] < 0) {
45
                    $subAuths[$i] = $subAuths[$i] + 0x100000000;
46
                }
47
            }
48
        }
49
        $revLevel = hexdec(substr($sidHex, 0, 2));
50
        $authIdent = hexdec(substr($sidHex, 4, 12));
51
52
        return 'S-' . $revLevel . '-' . $authIdent . '-' . implode('-', $subAuths);
53
    }
54
55
    protected function getCn($dn)
56
    {
57
        $dn = str_replace('\\,', '~C0mmA~', $dn);
58
        preg_match('/[^,]*/', $dn, $matches, PREG_OFFSET_CAPTURE, 3);
59
60
        return str_replace('~C0mmA~', ',', $matches[0][0]);
61
    }
62
63
    protected function getDn($samaccountname)
64
    {
65
        $link_identifier = $this->getConnection();
66
        $attributes = ['dn'];
67
        $result = ldap_search(
68
            $link_identifier,
69
            Config::get('auth_ad_base_dn'),
70
            $this->groupFilter($samaccountname),
71
            $attributes
72
        );
73
        $entries = ldap_get_entries($link_identifier, $result);
74
        if ($entries['count'] > 0) {
75
            return $entries[0]['dn'];
76
        } else {
77
            return '';
78
        }
79
    }
80
81
    protected function userFilter($username)
82
    {
83
        // don't return disabled users
84
        $user_filter = "(&(samaccountname=$username)(!(useraccountcontrol:1.2.840.113556.1.4.803:=2))";
85
86
        $extra = Config::get('auth_ad_user_filter');
87
        if ($extra) {
88
            $user_filter .= $extra;
89
        }
90
        $user_filter .= ')';
91
92
        return $user_filter;
93
    }
94
95
    protected function groupFilter($groupname)
96
    {
97
        $group_filter = "(samaccountname=$groupname)";
98
99
        $extra = Config::get('auth_ad_group_filter');
100
        if ($extra) {
101
            $group_filter = "(&$extra$group_filter)";
102
        }
103
104
        return $group_filter;
105
    }
106
107
    protected function getFullname($username)
108
    {
109
        $connection = $this->getConnection();
110
        $attributes = ['name'];
111
        $result = ldap_search(
112
            $connection,
113
            Config::get('auth_ad_base_dn'),
114
            $this->userFilter($username),
115
            $attributes
116
        );
117
        $entries = ldap_get_entries($connection, $result);
118
        if ($entries['count'] > 0) {
119
            $membername = $entries[0]['name'][0];
120
        } else {
121
            $membername = $username;
122
        }
123
124
        return $membername;
125
    }
126
127
    public function getGroupList()
128
    {
129
        $ldap_groups = [];
130
131
        // show all Active Directory Users by default
132
        $default_group = 'Users';
133
134
        if (Config::has('auth_ad_group')) {
135
            if (Config::get('auth_ad_group') !== $default_group) {
136
                $ldap_groups[] = Config::get('auth_ad_group');
137
            }
138
        }
139
140
        if (! Config::has('auth_ad_groups') && ! Config::has('auth_ad_group')) {
141
            $ldap_groups[] = $this->getDn($default_group);
142
        }
143
144
        foreach (Config::get('auth_ad_groups') as $key => $value) {
145
            $ldap_groups[] = $this->getDn($key);
146
        }
147
148
        return $ldap_groups;
149
    }
150
151
    public function getUserlist()
152
    {
153
        $connection = $this->getConnection();
154
155
        $userlist = [];
156
        $ldap_groups = $this->getGroupList();
157
158
        foreach ($ldap_groups as $ldap_group) {
159
            $search_filter = "(&(memberOf:1.2.840.113556.1.4.1941:=$ldap_group)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";
160
            if (Config::get('auth_ad_user_filter')) {
161
                $search_filter = '(&' . Config::get('auth_ad_user_filter') . $search_filter . ')';
162
            }
163
            $attributes = ['samaccountname', 'displayname', 'objectsid', 'mail'];
164
            $search = ldap_search($connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes);
165
            $results = ldap_get_entries($connection, $search);
166
167
            foreach ($results as $result) {
168
                if (isset($result['samaccountname'][0])) {
169
                    $userlist[$result['samaccountname'][0]] = $this->userFromAd($result);
170
                }
171
            }
172
        }
173
174
        return array_values($userlist);
175
    }
176
177
    /**
178
     * Generate a user array from an AD LDAP entry
179
     * Must have the attributes: objectsid, samaccountname, displayname, mail
180
     *
181
     * @internal
182
     *
183
     * @param  array  $entry
184
     * @return array
185
     */
186
    protected function userFromAd($entry)
187
    {
188
        return [
189
            'user_id' => $this->getUseridFromSid($this->sidFromLdap($entry['objectsid'][0])),
190
            'username' => $entry['samaccountname'][0],
191
            'realname' => $entry['displayname'][0],
192
            'email' => isset($entry['mail'][0]) ? $entry['mail'][0] : null,
193
            'descr' => '',
194
            'level' => $this->getUserlevel($entry['samaccountname'][0]),
195
            'can_modify_passwd' => 0,
196
        ];
197
    }
198
199
    public function getUser($user_id)
200
    {
201
        $connection = $this->getConnection();
202
        $domain_sid = $this->getDomainSid();
203
204
        $search_filter = "(&(objectcategory=person)(objectclass=user)(objectsid=$domain_sid-$user_id))";
205
        $attributes = ['samaccountname', 'displayname', 'objectsid', 'mail'];
206
        $search = ldap_search($connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes);
207
        $entry = ldap_get_entries($connection, $search);
208
209
        if (isset($entry[0]['samaccountname'][0])) {
210
            return $this->userFromAd($entry[0]);
211
        }
212
213
        return [];
214
    }
215
216
    protected function getDomainSid()
217
    {
218
        $connection = $this->getConnection();
219
220
        // Extract only the domain components
221
        $dn_candidate = preg_replace('/^.*?DC=/i', 'DC=', Config::get('auth_ad_base_dn'));
222
223
        $search = ldap_read(
224
            $connection,
225
            $dn_candidate,
226
            '(objectClass=*)',
227
            ['objectsid']
228
        );
229
        $entry = ldap_get_entries($connection, $search);
230
231
        return substr($this->sidFromLdap($entry[0]['objectsid'][0]), 0, 41);
232
    }
233
234
    /**
235
     * Provide a connected and bound ldap connection resource
236
     *
237
     * @return resource
238
     */
239
    abstract protected function getConnection();
240
}
241