Completed
Pull Request — master (#24)
by Patrick
02:53
created

LDAPAuthenticator::getAndBindServer()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 6
nop 1
dl 0
loc 27
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 * LDAPAuthenticator class
4
 *
5
 * This file describes the LDAPAuthenticator class
6
 *
7
 * PHP version 5 and 7
8
 *
9
 * @author Patrick Boyd / [email protected]
10
 * @copyright Copyright (c) 2015, Austin Artistic Reconstruction
11
 * @license http://www.apache.org/licenses/ Apache 2.0 License
12
 */
13
14
namespace Auth;
15
16
/**
17
 * Sort the provided array by the keys in $orderby
18
 *
19
 * @param array $array The array to sort
20
 * @param array $orderby An array of keys to sort the array by
21
 */
22
function sort_array(&$array, $orderby)
23
{
24
    $count = count($array);
25
    $keys  = array_keys($orderby);
26
    for($i = 0; $i < $count; $i++)
27
    {
28
        for($j = $i; $j < $count; $j++)
29
        {
30
            $data = strcasecmp($array[$i][$keys[0]][0], $array[$j][$keys[0]][0]);
31
            switch($orderby[$keys[0]])
32
            {
33
                case 1:
34
                    if($data > 0)
35
                    {
36
                        swap($array, $i, $j);
37
                    }
38
                    break;
39
                case 0:
40
                    if($data < 0)
41
                    {
42
                        swap($array, $i, $j);
43
                    }
44
                    break;
45
            }
46
        }
47
    }
48
}
49
50
/**
51
 * Swap two elements of the provided array
52
 *
53
 * @param array $array The array to swap values in
54
 * @param mixed $indexA The key of the first element to swap
55
 * @param mixed $indexB The key of the second element to swap
56
 */
57
function swap(&$array, $indexA, $indexB)
58
{
59
    $tmp = $array[$indexA];
60
    $array[$indexA] = $array[$indexB];
61
    $array[$indexB] = $tmp;
62
}
63
64
/**
65
 * An Authenticator class which uses LDAP as its backend storage mechanism
66
 */
67
class LDAPAuthenticator extends Authenticator
68
{
69
    /** The URL for the LDAP host */
70
    private $host;
71
    /** The base DN for all users in the LDAP server */
72
    public  $user_base;
73
    /** The base DN for all groups in the LDAP server */
74
    public  $group_base;
75
    /** The DN to use to bind if binding as the read/write user */
76
    private $bindDistinguishedName;
77
    /** The password to use to bind if binding as the read/write user */
78
    private $bindPassword;
79
    /** The DN to use to bind if binding as the read only user */
80
    private $bindRODistinguishedName;
81
    /** The password to use to bind if binding as the read only user */
82
    private $bindROPassword;
83
84
    /**
85
     * Create an LDAP Authenticator
86
     *
87
     * @param array $params Parementers needed to initialize the authenticator
88
     */
89
    public function __construct($params)
90
    {
91
        parent::__construct($params);
92
        $this->host       = $this->getHostParam($params);
93
        $this->user_base  = $this->getParam($params, 'user_base');
94
        $this->group_base = $this->getParam($params, 'group_base');
95
        $this->bindDistinguishedName = $this->getParam($params, 'bind_dn', '$ldap_auth', 'read_write_pass');
96
        $this->bindPassword = $this->getParam($params, 'bind_pass', '$ldap_auth', 'read_write_user');
97
        $this->bindRODistinguishedName = $this->getParam($params, 'ro_bind_dn', '$ldap_auth', 'read_only_pass');
98
        $this->bindROPassword = $this->getParam($params, 'ro_bind_pass', '$ldap_auth', 'read_only_user');
99
    }
100
101
    /**
102
     * Return the host string for this authenticator
103
     *
104
     * @param array $params The initial parameters of the authenticator
105
     *
106
     * @return string The host string
107
     *
108
     * @SuppressWarnings("StaticAccess")
109
     */
110
    private function getHostParam($params)
111
    {
112
        if(isset($params['host']))
113
        {
114
            return $params['host'];
115
        }
116
        $settings = \Settings::getInstance();
117
        return $settings->getLDAPSetting('host');
118
    }
119
120
    /**
121
     * Return the required paramter for this authenticator
122
     *
123
     * @param array  $params The initial parameters of the authenticator
124
     * @param string $paramName The name of the parameter in the $paramsArray
125
     * @param string $settingsLocation The location in the Settings class
126
     * @param string $settingsName The name in the Settings class
127
     *
128
     * @return mixed The paramter value
129
     *
130
     * @SuppressWarnings("StaticAccess")
131
     */
132
    private function getParam($params, $paramName, $settingsLocation = '$ldap', $settingsName = false)
133
    {
134
        if($settingsName === false)
135
        {
136
            $settingsName = $paramName;
137
        }
138
        if(isset($params[$paramName]))
139
        {
140
            return $params[$paramName];
141
        }
142
        $settings = \Settings::getInstance();
143
        return $settings->getLDAPSetting($settingsName, ($settingsLocation !== '$ldap'));
144
    }
145
146
    /**
147
     * Return an instance to the \LDAP\LDAPServer object instance
148
     *
149
     * @param boolean $bindWrite Should we be able to write to the server?
150
     *
151
     * @return \LDAP\LDAPServer|false The server instance if the binding was successful, otherwise false
152
     *
153
     * @SuppressWarnings("StaticAccess")
154
     */
155
    public function getAndBindServer($bindWrite = false)
156
    {
157
        $server = \LDAP\LDAPServer::getInstance();
158
        $server->user_base = $this->user_base;
159
        $server->group_base = $this->group_base;
160
        $server->connect($this->host);
161
        if($bindWrite === false)
162
        {
163
            if($this->bindRODistinguishedName && $this->bindROPassword)
164
            {
165
                $ret = $server->bind($this->bindRODistinguishedName, $this->bindROPassword);
166
            }
167
            else
168
            {
169
                $ret = $server->bind();
170
            }
171
        }
172
        else
173
        {
174
            $ret = $server->bind($this->bindDistinguishedName, $this->bindPassword);
175
        }
176
        if($ret === false)
177
        {
178
            return false;
179
        }
180
        return $server;
181
    }
182
183
    /**
184
     * Log the user in provided the credetials
185
     *
186
     * @param string $username The UID or email address for the user
187
     * @param string $password The password for the user
188
     *
189
     * @return mixed False if the login failed and array otherwise
190
     */
191
    public function login($username, $password)
192
    {
193
        $server = $this->getAndBindServer();
194
        if($server === false)
195
        {
196
            return false;
197
        }
198
        $filter = new \Data\Filter("uid eq $username or mail eq $username");
199
        $user = $server->read($this->user_base, $filter);
200
        if($user === false || count($user) === 0)
201
        {
202
            return false;
203
        }
204
        $user = $user[0];
205
        $server->unbind();
206
        $ret = $server->bind($user->dn, $password);
207
        if($ret !== false)
208
        {
209
            return array('res'=>true, 'extended'=>$user);
210
        }
211
        return false;
212
    }
213
214
    /**
215
     * Does this array represent a successful login?
216
     *
217
     * @param array $data The array data stored in the session after a login call
218
     *
219
     * @return boolean True if the user is logged in, false otherwise
220
     */
221
    public function isLoggedIn($data)
222
    {
223
        if(isset($data['res']))
224
        {
225
            return $data['res'];
226
        }
227
        return false;
228
    }
229
230
    /**
231
     * Obtain the currently logged in user from the session data
232
     *
233
     * @param \stdClass	$data The AuthData from the session
234
     *
235
     * @return null|\Auth\LDAPUser The LDAPUser represented by this data
236
     */
237
    public function getUser($data)
238
    {
239
        return new LDAPUser($data);
240
    }
241
242
    /**
243
     * Obtain the group based on the group name
244
     *
245
     * @param string $name The Group's name
246
     *
247
     * @return null|\Auth\LDAPGroup The LDAPGroup represented by the name or null if not found
248
     */
249
    public function getGroupByName($name)
250
    {
251
        $server = $this->getAndBindServer();
252
        if($server === false)
253
        {
254
            return null;
255
        }
256
        return LDAPGroup::from_name($name, $server);
257
    }
258
259
    public function getGroupsByFilter($filter, $select = false, $top = false, $skip = false, $orderby = false)
260
    {
261
        $server = $this->getAndBindServer();
262
        if($server === false)
263
        {
264
            return false;
265
        }
266
        if($filter === false)
267
        {
268
            $filter = new \Data\Filter('cn eq *');
269
        }
270
        $groups = $server->read($this->group_base, $filter);
271
        if($groups === false)
272
        {
273
            return false;
274
        }
275
        $this->processFilteringParams($groups, $select, $top, $skip, $orderby);
276
        $count = count($groups);
277
        for($i = 0; $i < $count; $i++)
278
        {
279
            $groups[$i] = new LDAPGroup($groups[$i]);
280
            if($select !== false)
281
            {
282
                $groups[$i] = json_decode(json_encode($groups[$i]), true);
283
                $groups[$i] = array_intersect_key($groups[$i], array_flip($select));
284
            }
285
        }
286
        return $groups;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $groups; (array) is incompatible with the return type of the parent method Auth\Authenticator::getGroupsByFilter of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
287
    }
288
289
    public function getActiveUserCount()
290
    {
291
        $server = $this->getAndBindServer();
292
        if($server === false)
293
        {
294
            return 0;
295
        }
296
        return $server->count($this->user_base);
297
    }
298
299
    /**
300
     * @param array           $data The array data to filter and sort
301
     * @param boolean|array   $select The fields to return
302
     * @param boolean|integer $top The number of records to return
303
     * @param boolean|integer $skip The number of records to skip
304
     * @param boolean|array   $orderby The fields to sort by
305
     */
306
    private function processFilteringParams(&$data, &$select, $top, $skip, $orderby)
307
    {
308
        if($orderby !== false)
309
        {
310
            sort_array($data, $orderby);
311
        }
312
        if($select !== false)
313
        {
314
            $select = array_flip($select);
315
        }
316
        if($skip !== false && $top !== false)
317
        {
318
            $data = array_slice($data, $skip, $top);
319
        }
320
        else if($top !== false)
321
        {
322
            $data = array_slice($data, 0, $top);
323
        }
324
        else if($skip !== false)
325
        {
326
            $data = array_slice($data, $skip);
327
        }
328
    }
329
330
331
    /**
332
     * @param boolean|\Data\Filter $filter The filter to user when reading users
333
     * @param boolean|array   $select The fields to return
334
     * @param boolean|integer $top The number of records to return
335
     * @param boolean|integer $skip The number of records to skip
336
     * @param boolean|array   $orderby The fields to sort by
337
     *
338
     * @return array|boolean False if no users found, an array of user objects otherwise
339
     */
340
    public function getUsersByFilter($filter, $select = false, $top = false, $skip = false, $orderby = false)
341
    {
342
        $server = $this->getAndBindServer();
343
        if($server === false)
344
        {
345
            return false;
346
        }
347
        if($filter === false)
348
        {
349
            $filter = new \Data\Filter('cn eq *');
350
        }
351
        $users = $server->read($this->user_base, $filter, false, $select);
352
        if($users === false)
353
        {
354
            return false;
355
        }
356
        $this->processFilteringParams($users, $select, $top, $skip, $orderby);
357
        $count = count($users);
358
        for($i = 0; $i < $count; $i++)
359
        {
360
            $tmp = new LDAPUser($users[$i]);
361
            if($select !== false)
362
            {
363
                $tmp = $tmp->jsonSerialize();
364
                $tmp = array_intersect_key($tmp, $select);
365
            }
366
            $users[$i] = $tmp;
367
        }
368
        return $users;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $users; (array) is incompatible with the return type of the parent method Auth\Authenticator::getUsersByFilter of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
369
    }
370
371
    public function activatePendingUser($user)
372
    {
373
        $this->getAndBindServer(true);
374
        $newUser = new LDAPUser();
375
        $newUser->uid = $user->uid;
376
        $newUser->mail = $user->mail;
377
        $newUser->sn = $user->sn;
378
        if(isset($user->givenName))
379
        {
380
            $newUser->givenName = $user->givenName;
381
        }
382
        if(isset($user->host))
383
        {
384
            $newUser->host = $user->host;
385
        }
386
        $pass = $user->getPassword();
387
        if($pass !== false)
388
        {
389
            $newUser->setPass($pass);
390
        }
391
        $ret = $newUser->flushUser();
392
        if($ret)
393
        {
394
            $user->delete();
395
        }
396
        $users = $this->getUsersByFilter(new \Data\Filter('mail eq '.$user->mail));
397
        if(empty($users))
398
        {
399
            throw new \Exception('Error creating user!');
400
        }
401
        return $users[0];
402
    }
403
404
    public function getUserByResetHash($hash)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
405
    {
406
        $users = $this->getUsersByFilter(new \Data\Filter("uniqueIdentifier eq $hash"));
407
        if($users === false || !isset($users[0]))
408
        {
409
            return false;
410
        }
411
        return $users[0];
412
    }
413
}
414
/* vim: set tabstop=4 shiftwidth=4 expandtab: */
415