Completed
Pull Request — master (#24)
by Patrick
07:23 queued 04:50
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
207
        $ret = $server->bind($user->dn, $password);
208
        if($ret !== false)
209
        {
210
            return array('res'=>true, 'extended'=>$user);
211
        }
212
        $server->unbind();
213
        return false;
214
    }
215
216
    /**
217
     * Does this array represent a successful login?
218
     *
219
     * @param array $data The array data stored in the session after a login call
220
     *
221
     * @return boolean True if the user is logged in, false otherwise
222
     */
223
    public function isLoggedIn($data)
224
    {
225
        if(isset($data['res']))
226
        {
227
            return $data['res'];
228
        }
229
        return false;
230
    }
231
232
    /**
233
     * Obtain the currently logged in user from the session data
234
     *
235
     * @param \stdClass	$data The AuthData from the session
236
     *
237
     * @return null|\Auth\LDAPUser The LDAPUser represented by this data
238
     */
239
    public function getUser($data)
240
    {
241
        return new LDAPUser($data);
242
    }
243
244
    /**
245
     * Obtain the group based on the group name
246
     *
247
     * @param string $name The Group's name
248
     *
249
     * @return null|\Auth\LDAPGroup The LDAPGroup represented by the name or null if not found
250
     */
251
    public function getGroupByName($name)
252
    {
253
        $server = $this->getAndBindServer();
254
        if($server === false)
255
        {
256
            return null;
257
        }
258
        return LDAPGroup::from_name($name, $server);
259
    }
260
261
    public function getGroupsByFilter($filter, $select = false, $top = false, $skip = false, $orderby = false)
262
    {
263
        $server = $this->getAndBindServer();
264
        if($server === false)
265
        {
266
            return false;
267
        }
268
        if($filter === false)
269
        {
270
            $filter = new \Data\Filter('cn eq *');
271
        }
272
        $groups = $server->read($this->group_base, $filter);
273
        if($groups === false)
274
        {
275
            return false;
276
        }
277
        $this->processFilteringParams($groups, $select, $top, $skip, $orderby);
278
        $count = count($groups);
279
        for($i = 0; $i < $count; $i++)
280
        {
281
            $groups[$i] = new LDAPGroup($groups[$i]);
282
            if($select !== false)
283
            {
284
                $groups[$i] = json_decode(json_encode($groups[$i]), true);
285
                $groups[$i] = array_intersect_key($groups[$i], $select);
286
            }
287
        }
288
        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...
289
    }
290
291
    public function getActiveUserCount()
292
    {
293
        $server = $this->getAndBindServer();
294
        if($server === false)
295
        {
296
            return 0;
297
        }
298
        return $server->count($this->user_base);
299
    }
300
301
    /**
302
     * @param array           $data The array data to filter and sort
303
     * @param boolean|array   $select The fields to return
304
     * @param boolean|integer $top The number of records to return
305
     * @param boolean|integer $skip The number of records to skip
306
     * @param boolean|array   $orderby The fields to sort by
307
     */
308
    private function processFilteringParams(&$data, &$select, $top, $skip, $orderby)
309
    {
310
        if($orderby !== false)
311
        {
312
            sort_array($data, $orderby);
313
        }
314
        if($select !== false)
315
        {
316
            $select = array_flip($select);
317
        }
318
        if($skip !== false && $top !== false)
319
        {
320
            $data = array_slice($data, $skip, $top);
321
        }
322
        else if($top !== false)
323
        {
324
            $data = array_slice($data, 0, $top);
325
        }
326
        else if($skip !== false)
327
        {
328
            $data = array_slice($data, $skip);
329
        }
330
    }
331
332
333
    /**
334
     * @param boolean|\Data\Filter $filter The filter to user when reading users
335
     * @param boolean|array   $select The fields to return
336
     * @param boolean|integer $top The number of records to return
337
     * @param boolean|integer $skip The number of records to skip
338
     * @param boolean|array   $orderby The fields to sort by
339
     *
340
     * @return array|boolean False if no users found, an array of user objects otherwise
341
     */
342
    public function getUsersByFilter($filter, $select = false, $top = false, $skip = false, $orderby = false)
343
    {
344
        $server = $this->getAndBindServer();
345
        if($server === false)
346
        {
347
            return false;
348
        }
349
        if($filter === false)
350
        {
351
            $filter = new \Data\Filter('cn eq *');
352
        }
353
        $users = $server->read($this->user_base, $filter, false, $select);
354
        if($users === false)
355
        {
356
            return false;
357
        }
358
        $this->processFilteringParams($users, $select, $top, $skip, $orderby);
359
        $count = count($users);
360
        for($i = 0; $i < $count; $i++)
361
        {
362
            $tmp = new LDAPUser($users[$i]);
363
            if($select !== false)
364
            {
365
                $tmp = $tmp->jsonSerialize();
366
                $tmp = array_intersect_key($tmp, $select);
367
            }
368
            $users[$i] = $tmp;
369
        }
370
        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...
371
    }
372
373
    public function activatePendingUser($user)
374
    {
375
        $this->getAndBindServer(true);
376
        $newUser = new LDAPUser();
377
        $newUser->uid = $user->uid;
378
        $newUser->mail = $user->mail;
379
        $newUser->sn = $user->sn;
380
        if(isset($user->givenName))
381
        {
382
            $newUser->givenName = $user->givenName;
383
        }
384
        if(isset($user->host))
385
        {
386
            $newUser->host = $user->host;
387
        }
388
        $pass = $user->getPassword();
389
        if($pass !== false)
390
        {
391
            $newUser->setPass($pass);
392
        }
393
        $ret = $newUser->flushUser();
394
        if($ret)
395
        {
396
            $user->delete();
397
        }
398
        $users = $this->getUsersByFilter(new \Data\Filter('mail eq '.$user->mail));
399
        if(empty($users))
400
        {
401
            throw new \Exception('Error creating user!');
402
        }
403
        return $users[0];
404
    }
405
406
    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...
407
    {
408
        $users = $this->getUsersByFilter(new \Data\Filter("uniqueIdentifier eq $hash"));
409
        if($users === false || !isset($users[0]))
410
        {
411
            return false;
412
        }
413
        return $users[0];
414
    }
415
}
416
/* vim: set tabstop=4 shiftwidth=4 expandtab: */
417