Issues (2963)

Authentication/LdapAuthorizationAuthorizer.php (2 issues)

1
<?php
2
/*
3
 * This program is free software: you can redistribute it and/or modify
4
 * it under the terms of the GNU General Public License as published by
5
 * the Free Software Foundation, either version 3 of the License, or
6
 * (at your option) any later version.
7
 *
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
11
 * GNU General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU General Public License
14
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
 */
16
17
/**
18
 * libreNMS HTTP-Authentication and LDAP Authorization Library
19
 *
20
 * @author Maximilian Wilhelm <[email protected]>
21
 * @copyright 2016 LibreNMS, Barbarossa
22
 * @license GPL
23
 */
24
25
namespace LibreNMS\Authentication;
26
27
use App\Models\User;
28
use LibreNMS\Config;
29
use LibreNMS\Exceptions\AuthenticationException;
30
use LibreNMS\Exceptions\LdapMissingException;
31
32
class LdapAuthorizationAuthorizer extends AuthorizerBase
33
{
34
    use LdapSessionCache;
35
36
    protected $ldap_connection;
37
    protected static $AUTH_IS_EXTERNAL = true;
38
39
    public function __construct()
40
    {
41
        if (! function_exists('ldap_connect')) {
42
            throw new LdapMissingException();
43
        }
44
45
        /**
46
         * Set up connection to LDAP server
47
         */
48
        $this->ldap_connection = @ldap_connect(Config::get('auth_ldap_server'), Config::get('auth_ldap_port'));
0 ignored issues
show
It seems like LibreNMS\Config::get('auth_ldap_port') can also be of type null; however, parameter $port of ldap_connect() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

48
        $this->ldap_connection = @ldap_connect(Config::get('auth_ldap_server'), /** @scrutinizer ignore-type */ Config::get('auth_ldap_port'));
Loading history...
49
        if (! $this->ldap_connection) {
50
            throw new AuthenticationException('Fatal error while connecting to LDAP server ' . Config::get('auth_ldap_server') . ':' . Config::get('auth_ldap_port') . ': ' . ldap_error($this->ldap_connection));
51
        }
52
        if (Config::get('auth_ldap_version')) {
53
            ldap_set_option($this->ldap_connection, LDAP_OPT_PROTOCOL_VERSION, Config::get('auth_ldap_version'));
54
        }
55
56
        if (Config::get('auth_ldap_starttls') && (Config::get('auth_ldap_starttls') == 'optional' || Config::get('auth_ldap_starttls') == 'require')) {
57
            $tls = ldap_start_tls($this->ldap_connection);
58
            if (Config::get('auth_ldap_starttls') == 'require' && $tls === false) {
59
                throw new AuthenticationException('Fatal error: LDAP TLS required but not successfully negotiated:' . ldap_error($this->ldap_connection));
60
            }
61
        }
62
    }
63
64
    public function authenticate($credentials)
65
    {
66
        if (isset($credentials['username']) && $this->userExists($credentials['username'])) {
67
            return true;
68
        }
69
70
        $guest = Config::get('http_auth_guest');
71
        if ($guest && User::thisAuth()->where('username', $guest)->exists()) {
72
            return true;
73
        }
74
75
        throw new AuthenticationException();
76
    }
77
78
    public function userExists($username, $throw_exception = false)
79
    {
80
        if ($this->authLdapSessionCacheGet('user_exists')) {
81
            return true;
82
        }
83
84
        $filter = '(' . Config::get('auth_ldap_prefix') . $username . ')';
85
        $search = ldap_search($this->ldap_connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
86
        $entries = ldap_get_entries($this->ldap_connection, $search);
87
        if ($entries['count']) {
88
            /*
89
         * Cache positiv result as this will result in more queries which we
90
         * want to speed up.
91
         */
92
            $this->authLdapSessionCacheSet('user_exists', 1);
93
94
            return true;
95
        }
96
97
        /*
98
         * Don't cache that user doesn't exists as this might be a misconfiguration
99
         * on some end and the user will be happy if it "just works" after the user
100
         * has been added to LDAP.
101
         */
102
        return false;
103
    }
104
105
    public function getUserlevel($username)
106
    {
107
        $userlevel = $this->authLdapSessionCacheGet('userlevel');
108
        if ($userlevel) {
109
            return $userlevel;
110
        } else {
111
            $userlevel = 0;
112
        }
113
114
        // Find all defined groups $username is in
115
        $filter = '(&(|(cn=' . join(')(cn=', array_keys(Config::get('auth_ldap_groups'))) . '))(' . Config::get('auth_ldap_groupmemberattr') . '=' . $this->getMembername($username) . '))';
116
        $search = ldap_search($this->ldap_connection, Config::get('auth_ldap_groupbase'), $filter);
117
        $entries = ldap_get_entries($this->ldap_connection, $search);
118
119
        // Loop the list and find the highest level
120
        foreach ($entries as $entry) {
121
            $groupname = $entry['cn'][0];
122
            $authLdapGroups = Config::get('auth_ldap_groups');
123
            if ($authLdapGroups[$groupname]['level'] > $userlevel) {
124
                $userlevel = $authLdapGroups[$groupname]['level'];
125
            }
126
        }
127
128
        $this->authLdapSessionCacheSet('userlevel', $userlevel);
129
130
        return $userlevel;
131
    }
132
133
    public function getUserid($username)
134
    {
135
        $user_id = $this->authLdapSessionCacheGet('userid');
136
        if (isset($user_id)) {
137
            return $user_id;
138
        }
139
140
        $guest_username = Config::get('http_auth_guest');
141
        $user_id = User::thisAuth()->where('username', $guest_username)->value('auth_id') ?: -1;
142
143
        $filter = '(' . Config::get('auth_ldap_prefix') . $username . ')';
144
        $search = ldap_search($this->ldap_connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
145
        $entries = ldap_get_entries($this->ldap_connection, $search);
146
147
        if ($entries['count']) {
148
            $user_id = (int) $entries[0]['uidnumber'][0];
149
        }
150
151
        if ($user_id === -1) {
152
            // no user or guest user, don't allow
153
            if ($guest_username) {
154
                throw new AuthenticationException();
155
            } else {
156
                throw new AuthenticationException('Guest login allowed.');
157
            }
158
        }
159
160
        $this->authLdapSessionCacheSet('userid', $user_id);
161
162
        return $user_id;
163
    }
164
165
    public function getUserlist()
166
    {
167
        $userlist = [];
168
169
        $filter = '(' . Config::get('auth_ldap_prefix') . '*)';
170
171
        $search = ldap_search($this->ldap_connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
172
        $entries = ldap_get_entries($this->ldap_connection, $search);
173
174
        if ($entries['count']) {
175
            foreach ($entries as $entry) {
176
                $username = $entry['uid'][0];
177
                $realname = $entry['cn'][0];
178
                $user_id = $entry['uidnumber'][0];
179
                $email = $entry[Config::get('auth_ldap_emailattr')][0];
180
                $ldap_groups = $this->getGroupList();
181
                foreach ($ldap_groups as $ldap_group) {
182
                    $ldap_comparison = ldap_compare(
183
                        $this->ldap_connection,
184
                        $ldap_group,
185
                        Config::get('auth_ldap_groupmemberattr'),
0 ignored issues
show
It seems like LibreNMS\Config::get('auth_ldap_groupmemberattr') can also be of type null; however, parameter $attribute of ldap_compare() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

185
                        /** @scrutinizer ignore-type */ Config::get('auth_ldap_groupmemberattr'),
Loading history...
186
                        $this->getMembername($username)
187
                    );
188
                    if (! Config::has('auth_ldap_group') || $ldap_comparison === true) {
189
                        $userlist[] = [
190
                            'username' => $username,
191
                            'realname' => $realname,
192
                            'user_id'  => $user_id,
193
                            'email'    => $email,
194
                        ];
195
                    }
196
                }
197
            }
198
        }
199
200
        return $userlist;
201
    }
202
203
    public function getUser($user_id)
204
    {
205
        foreach ($this->getUserlist() as $user) {
206
            if ((int) $user['user_id'] === (int) $user_id) {
207
                $user['level'] = $this->getUserlevel($user['username']);
208
209
                return $user;
210
            }
211
        }
212
213
        return false;
214
    }
215
216
    protected function getMembername($username)
217
    {
218
        if (Config::get('auth_ldap_groupmembertype') == 'fulldn') {
219
            $membername = Config::get('auth_ldap_prefix') . $username . Config::get('auth_ldap_suffix');
220
        } elseif (Config::get('auth_ldap_groupmembertype') == 'puredn') {
221
            $filter = '(' . Config::get('auth_ldap_attr.uid') . '=' . $username . ')';
222
            $search = ldap_search($this->ldap_connection, Config::get('auth_ldap_groupbase'), $filter);
223
            $entries = ldap_get_entries($this->ldap_connection, $search);
224
            $membername = $entries[0]['dn'];
225
        } else {
226
            $membername = $username;
227
        }
228
229
        return $membername;
230
    }
231
232
    public function getGroupList()
233
    {
234
        $ldap_groups = [];
235
        $default_group = 'cn=groupname,ou=groups,dc=example,dc=com';
236
        if (Config::has('auth_ldap_group')) {
237
            if (Config::get('auth_ldap_group') !== $default_group) {
238
                $ldap_groups[] = Config::get('auth_ldap_group');
239
            }
240
        }
241
242
        foreach (Config::get('auth_ldap_groups') as $key => $value) {
243
            $dn = "cn=$key," . Config::get('auth_ldap_groupbase');
244
            $ldap_groups[] = $dn;
245
        }
246
247
        return $ldap_groups;
248
    }
249
}
250