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); |
|
|
|
|
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); |
|
|
|
|
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; |
|
|
|
|
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; |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
|
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:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.