Completed
Push — master ( f22a46...046041 )
by Patrick
02:27 queued 12s
created

LDAPAuthenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 11
rs 9.9
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 Flipside\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
        return false;
213
    }
214
215
    /**
216
     * Does this array represent a successful login?
217
     *
218
     * @param array $data The array data stored in the session after a login call
219
     *
220
     * @return boolean True if the user is logged in, false otherwise
221
     */
222
    public function isLoggedIn($data)
223
    {
224
        if(isset($data['res']))
225
        {
226
            return $data['res'];
227
        }
228
        return false;
229
    }
230
231
    /**
232
     * Obtain the currently logged in user from the session data
233
     *
234
     * @param \stdClass	$data The AuthData from the session
235
     *
236
     * @return null|\Auth\LDAPUser The LDAPUser represented by this data
237
     */
238
    public function getUser($data)
239
    {
240
        return new LDAPUser($data);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \Flipside\Auth\LDAPUser($data); (Flipside\Auth\LDAPUser) is incompatible with the return type of the parent method Flipside\Auth\Authenticator::getUser of type null|Auth\User.

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...
241
    }
242
243
    /**
244
     * Obtain the group based on the group name
245
     *
246
     * @param string $name The Group's name
247
     *
248
     * @return null|\Auth\LDAPGroup The LDAPGroup represented by the name or null if not found
249
     */
250
    public function getGroupByName($name)
251
    {
252
        $server = $this->getAndBindServer();
253
        if($server === false)
254
        {
255
            return null;
256
        }
257
        return LDAPGroup::from_name($name, $server);
0 ignored issues
show
Bug Compatibility introduced by
The expression \Flipside\Auth\LDAPGroup...m_name($name, $server); of type null|Flipside\Auth\LDAPGroup adds the type Flipside\Auth\LDAPGroup to the return on line 257 which is incompatible with the return type of the parent method Flipside\Auth\Authenticator::getGroupByName of type null|Auth\Group.
Loading history...
258
    }
259
260
    public function getGroupsByFilter($filter, $select = false, $top = false, $skip = false, $orderby = false)
261
    {
262
        $server = $this->getAndBindServer();
263
        if($server === false)
264
        {
265
            return false;
266
        }
267
        if($filter === false)
268
        {
269
            $filter = new \Data\Filter('cn eq *');
270
        }
271
        $groups = $server->read($this->group_base, $filter);
272
        if($groups === false)
273
        {
274
            return false;
275
        }
276
        $this->processFilteringParams($groups, $select, $top, $skip, $orderby);
277
        $count = count($groups);
278
        for($i = 0; $i < $count; $i++)
279
        {
280
            $groups[$i] = new LDAPGroup($groups[$i]);
281
            if($select !== false)
282
            {
283
                $groups[$i] = json_decode(json_encode($groups[$i]), true);
284
                $groups[$i] = array_intersect_key($groups[$i], $select);
285
            }
286
        }
287
        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 Flipside\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...
288
    }
289
290
    public function getActiveUserCount()
291
    {
292
        $server = $this->getAndBindServer();
293
        if($server === false)
294
        {
295
            return 0;
296
        }
297
        return $server->count($this->user_base);
298
    }
299
300
    /**
301
     * @param array           $data The array data to filter and sort
302
     * @param boolean|array   $select The fields to return
303
     * @param boolean|integer $top The number of records to return
304
     * @param boolean|integer $skip The number of records to skip
305
     * @param boolean|array   $orderby The fields to sort by
306
     */
307
    private function processFilteringParams(&$data, &$select, $top, $skip, $orderby)
308
    {
309
        if($orderby !== false)
310
        {
311
            sort_array($data, $orderby);
312
        }
313
        if($select !== false)
314
        {
315
            $select = array_flip($select);
316
        }
317
        if($skip !== false && $top !== false)
318
        {
319
            $data = array_slice($data, $skip, $top);
320
        }
321
        else if($top !== false)
322
        {
323
            $data = array_slice($data, 0, $top);
324
        }
325
        else if($skip !== false)
326
        {
327
            $data = array_slice($data, $skip);
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 Flipside\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
        if(isset($user->sn))
378
        {
379
            $newUser->sn = $user->sn;
380
        }
381
        else
382
        {
383
            $newUser->sn = $newUser->uid;
384
        }
385
        if(isset($user->givenName))
386
        {
387
            $newUser->givenName = $user->givenName;
388
        }
389
        if(isset($user->host))
390
        {
391
            $newUser->host = $user->host;
392
        }
393
        $pass = $user->getPassword();
394
        if($pass !== false)
395
        {
396
            $newUser->setPass($pass);
397
        }
398
        $ret = $newUser->flushUser();
399
        if($ret)
400
        {
401
            $user->delete();
402
        }
403
        $users = $this->getUsersByFilter(new \Data\Filter('mail eq '.$user->mail));
404
        if(empty($users))
405
        {
406
            throw new \Exception('Error creating user!');
407
        }
408
        return $users[0];
409
    }
410
411
    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...
412
    {
413
        $users = $this->getUsersByFilter(new \Data\Filter("uniqueIdentifier eq $hash"));
414
        if($users === false || !isset($users[0]))
415
        {
416
            return false;
417
        }
418
        return $users[0];
419
    }
420
}
421
/* vim: set tabstop=4 shiftwidth=4 expandtab: */
422