1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This base LDAP filter class can be extended to enable real |
5
|
|
|
* filter classes direct access to the authsource ldap config |
6
|
|
|
* and connects to the ldap server. |
7
|
|
|
* |
8
|
|
|
* Updated: 20161223 Remy Blom |
9
|
|
|
* - Wrapped the building of authsource config with issets |
10
|
|
|
* |
11
|
|
|
* @package SimpleSAMLphp |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
declare(strict_types=1); |
15
|
|
|
|
16
|
|
|
namespace SimpleSAML\Module\ldap\Auth\Process; |
17
|
|
|
|
18
|
|
|
use SimpleSAML\Configuration; |
19
|
|
|
use SimpleSAML\Error; |
20
|
|
|
use SimpleSAML\Logger; |
21
|
|
|
use SimpleSAML\Module\ldap\Auth\Ldap; |
22
|
|
|
use SimpleSAML\Module\ldap\Utils; |
23
|
|
|
|
24
|
|
|
abstract class BaseFilter extends \SimpleSAML\Auth\ProcessingFilter |
25
|
|
|
{ |
26
|
|
|
// TODO: Support ldap:LDAPMulti, if possible |
27
|
|
|
protected static array $ldapsources = ['ldap:Ldap', 'authX509:X509userCert']; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* List of attribute "alias's" linked to the real attribute |
31
|
|
|
* name. Used for abstraction / configuration of the LDAP |
32
|
|
|
* attribute names, which may change between dir service. |
33
|
|
|
* |
34
|
|
|
* @var array |
35
|
|
|
*/ |
36
|
|
|
protected array $attribute_map; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* The base DN of the LDAP connection. Used when searching the LDAP server. |
40
|
|
|
* |
41
|
|
|
* @var array |
42
|
|
|
*/ |
43
|
|
|
protected $searchBase; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* The construct method will change the filter config into |
47
|
|
|
* a \SimpleSAML\Configuration object and store it here for |
48
|
|
|
* later use, if needed. |
49
|
|
|
* |
50
|
|
|
* @var \SimpleSAML\Configuration |
51
|
|
|
*/ |
52
|
|
|
protected Configuration $config; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Array of LDAP connection objects. Stored here to be accessed later during processing. |
56
|
|
|
* |
57
|
|
|
* @var \Symfony\Component\Ldap\Ldap[] |
58
|
|
|
*/ |
59
|
|
|
protected array $ldapServers; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* The class "title" used in logging and exception messages. |
63
|
|
|
* This should be prepended to the beginning of the message. |
64
|
|
|
* |
65
|
|
|
* @var string |
66
|
|
|
*/ |
67
|
|
|
protected string $title = 'ldap:BaseFilter'; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* List of LDAP object types, used to determine the type of |
71
|
|
|
* object that a DN references. |
72
|
|
|
* |
73
|
|
|
* @var array |
74
|
|
|
*/ |
75
|
|
|
protected array $type_map; |
76
|
|
|
|
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Checks the authsource, if defined, for configuration values |
80
|
|
|
* to the LDAP server. Then sets up the LDAP connection for the |
81
|
|
|
* instance/object and stores everything in class members. |
82
|
|
|
* |
83
|
|
|
* @throws \SimpleSAML\Error\Exception |
84
|
|
|
* @param array &$config |
85
|
|
|
* @param mixed $reserved |
86
|
|
|
*/ |
87
|
|
|
public function __construct(array &$config, $reserved) |
88
|
|
|
{ |
89
|
|
|
parent::__construct($config, $reserved); |
90
|
|
|
|
91
|
|
|
// Change the class $title to match it's true name |
92
|
|
|
// This way if the class is extended the proper name is used |
93
|
|
|
$classname = get_class($this); |
94
|
|
|
$classname = explode('_', $classname); |
95
|
|
|
$this->title = 'ldap:' . end($classname); |
96
|
|
|
|
97
|
|
|
// Log the construction |
98
|
|
|
Logger::debug(sprintf('%s : Creating and configuring the filter.', $this->title)); |
99
|
|
|
|
100
|
|
|
// If an authsource was defined (an not empty string)... |
101
|
|
|
if (isset($config['authsource']) && $config['authsource'] !== '') { |
102
|
|
|
$authconfig = $this->parseAuthSourceConfig($config['authsource']); |
103
|
|
|
|
104
|
|
|
// Merge the authsource config with the filter config, |
105
|
|
|
// but have the filter config override the authsource config |
106
|
|
|
$config = array_merge($authconfig, $config); |
107
|
|
|
|
108
|
|
|
// Authsource complete |
109
|
|
|
Logger::debug(sprintf( |
110
|
|
|
'%s : Retrieved authsource [%s] configuration values: %s', |
111
|
|
|
$this->title, |
112
|
|
|
$config['authsource'], |
113
|
|
|
$this->varExport($authconfig) |
114
|
|
|
)); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
// Convert the config array to a config class, |
118
|
|
|
// that way we can verify type and define defaults. |
119
|
|
|
// Store in the instance in-case needed later, by a child class. |
120
|
|
|
$this->config = Configuration::loadFromArray($config, 'ldap:AuthProcess'); |
121
|
|
|
|
122
|
|
|
// Initialize the Ldap-object |
123
|
|
|
$this->ldapServers = $this->initializeLdap(); |
124
|
|
|
|
125
|
|
|
// Set all the filter values, setting defaults if needed |
126
|
|
|
$this->searchBase = $this->config->getArrayizeString('search.base', ''); |
|
|
|
|
127
|
|
|
|
128
|
|
|
// Log the member values retrieved above |
129
|
|
|
Logger::debug(sprintf( |
130
|
|
|
'%s : Configuration values retrieved; BaseDN: %s', |
131
|
|
|
$this->title, |
132
|
|
|
$this->varExport($this->searchBase) |
133
|
|
|
)); |
134
|
|
|
|
135
|
|
|
// Setup the attribute map which will be used to search LDAP |
136
|
|
|
$this->attribute_map = [ |
137
|
|
|
'dn' => $this->config->getString('attribute.dn', 'distinguishedName'), |
138
|
|
|
'groups' => $this->config->getString('attribute.groups', 'groups'), |
139
|
|
|
'member' => $this->config->getString('attribute.member', 'member'), |
140
|
|
|
'memberof' => $this->config->getString('attribute.memberof', 'memberOf'), |
141
|
|
|
'name' => $this->config->getString('attribute.groupname', 'name'), |
142
|
|
|
'return' => $this->config->getString('attribute.return', 'distinguishedName'), |
143
|
|
|
'type' => $this->config->getString('attribute.type', 'objectClass'), |
144
|
|
|
'username' => $this->config->getString('attribute.username', 'sAMAccountName') |
145
|
|
|
]; |
146
|
|
|
|
147
|
|
|
// Log the attribute map |
148
|
|
|
Logger::debug(sprintf( |
149
|
|
|
'%s : Attribute map created: $s', |
150
|
|
|
$this->title, |
151
|
|
|
$this->varExport($this->attribute_map) |
152
|
|
|
)); |
153
|
|
|
|
154
|
|
|
// Setup the object type map which is used to determine a DNs' type |
155
|
|
|
$this->type_map = [ |
156
|
|
|
'group' => $this->config->getString('type.group', 'group'), |
157
|
|
|
'user' => $this->config->getString('type.user', 'user') |
158
|
|
|
]; |
159
|
|
|
|
160
|
|
|
// Log the type map |
161
|
|
|
Logger::debug(sprintf( |
162
|
|
|
'%s : Type map created: %s', |
163
|
|
|
$this->title, |
164
|
|
|
$this->varExport($this->type_map) |
165
|
|
|
)); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Parse authsource config |
171
|
|
|
* |
172
|
|
|
* @param string $as The name of the authsource |
173
|
|
|
*/ |
174
|
|
|
private function parseAuthSourceConfig(string $as): array |
175
|
|
|
{ |
176
|
|
|
// Log the authsource request |
177
|
|
|
Logger::debug(sprintf( |
178
|
|
|
'%s : Attempting to get configuration values from authsource [%s]', |
179
|
|
|
$this->title, |
180
|
|
|
$as |
181
|
|
|
)); |
182
|
|
|
|
183
|
|
|
// Get the authsources file, which should contain the config |
184
|
|
|
$authsources = Configuration::getConfig('authsources.php'); |
185
|
|
|
|
186
|
|
|
// Verify that the authsource config exists |
187
|
|
|
if (!$authsources->hasValue($as)) { |
188
|
|
|
throw new Error\Exception(sprintf( |
189
|
|
|
'%s : Authsource [%s] defined in filter parameters not found in authsources.php', |
190
|
|
|
$this->title, |
191
|
|
|
$as |
192
|
|
|
)); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
// Get just the specified authsource config values |
196
|
|
|
$authsource = $authsources->getArray($as); |
197
|
|
|
|
198
|
|
|
// Make sure it is an ldap source |
199
|
|
|
if (isset($authsource[0]) && !in_array($authsource[0], self::$ldapsources)) { |
200
|
|
|
throw new Error\Exception(sprintf( |
201
|
|
|
'%s : Authsource [%s] specified in filter parameters is not an ldap:LDAP type', |
202
|
|
|
$this->title, |
203
|
|
|
$as |
204
|
|
|
)); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
// Build the authsource config |
208
|
|
|
$authconfig = []; |
209
|
|
|
if (isset($authsource['connection_string'])) { |
210
|
|
|
$authconfig['connection_string'] = $authsource['connection_string']; |
211
|
|
|
} |
212
|
|
|
if (isset($authsource['encryption'])) { |
213
|
|
|
$authconfig['encryption'] = $authsource['encryption']; |
214
|
|
|
} |
215
|
|
|
if (isset($authsource['version'])) { |
216
|
|
|
$authconfig['version'] = $authsource['version']; |
217
|
|
|
} |
218
|
|
|
if (isset($authsource['timeout'])) { |
219
|
|
|
$authconfig['timeout'] = $authsource['timeout']; |
220
|
|
|
} |
221
|
|
|
if (isset($authsource['debug'])) { |
222
|
|
|
$authconfig['debug'] = $authsource['debug']; |
223
|
|
|
} |
224
|
|
|
if (isset($authsource['referrals'])) { |
225
|
|
|
$authconfig['referrals'] = $authsource['referrals']; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
// only set when search.enabled = true |
229
|
|
|
if (isset($authsource['search.enable']) && ($authsource['search.enable'] === true)) { |
230
|
|
|
if (isset($authsource['search.base'])) { |
231
|
|
|
$authconfig['search.base'] = $authsource['search.base']; |
232
|
|
|
} |
233
|
|
|
if (isset($authsource['search.scope'])) { |
234
|
|
|
$authconfig['search.scope'] = $authsource['search.scope']; |
235
|
|
|
} |
236
|
|
|
if (isset($authsource['search.username'])) { |
237
|
|
|
$authconfig['search.username'] = $authsource['search.username']; |
238
|
|
|
} |
239
|
|
|
if (isset($authsource['search.password'])) { |
240
|
|
|
$authconfig['search.password'] = $authsource['search.password']; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
// Only set the username attribute if the authsource specifies one attribute |
244
|
|
|
if ( |
245
|
|
|
isset($authsource['search.attributes']) |
246
|
|
|
&& is_array($authsource['search.attributes']) |
247
|
|
|
&& count($authsource['search.attributes']) == 1 |
248
|
|
|
) { |
249
|
|
|
$authconfig['attribute.username'] = reset($authsource['search.attributes']); |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
// only set when priv.read = true |
254
|
|
|
if (isset($authsource['priv.read']) && $authsource['priv.read']) { |
255
|
|
|
if (isset($authsource['priv.username'])) { |
256
|
|
|
$authconfig['priv.username'] = $authsource['priv.username']; |
257
|
|
|
} |
258
|
|
|
if (isset($authsource['priv.password'])) { |
259
|
|
|
$authconfig['priv.password'] = $authsource['priv.password']; |
260
|
|
|
} |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
return $authconfig; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Initialize the Ldap-object |
269
|
|
|
* |
270
|
|
|
* @return array |
271
|
|
|
*/ |
272
|
|
|
private function initializeLdap(): array |
273
|
|
|
{ |
274
|
|
|
$ldapUtils = new Utils\Ldap(); |
275
|
|
|
|
276
|
|
|
return $ldapUtils->create( |
277
|
|
|
explode(' ', $this->config->getString('connection_string')), |
278
|
|
|
$this->config->getString('encryption', 'ssl'), |
279
|
|
|
$this->config->getInteger('version', 3), |
280
|
|
|
$this->config->getString('extension', 'ext_ldap'), |
281
|
|
|
$this->config->getBoolean('debug', false), |
282
|
|
|
[ |
283
|
|
|
'network_timeout' => $this->config->getInteger('timeout', 3), |
284
|
|
|
'referrals' => $this->config->getBoolean('referrals', false), |
285
|
|
|
] |
286
|
|
|
); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Local utility function to get details about a variable, |
292
|
|
|
* basically converting it to a string to be used in a log |
293
|
|
|
* message. The var_export() function returns several lines |
294
|
|
|
* so this will remove the new lines and trim each line. |
295
|
|
|
* |
296
|
|
|
* @param mixed $value |
297
|
|
|
* @return string |
298
|
|
|
*/ |
299
|
|
|
protected function varExport($value): string |
300
|
|
|
{ |
301
|
|
|
if (is_array($value)) { |
302
|
|
|
// remove sensitive data |
303
|
|
|
foreach ($value as $key => &$val) { |
304
|
|
|
if ($key === 'search.password') { |
305
|
|
|
$val = empty($val) ? '' : '********'; |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
unset($val); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
$export = var_export($value, true); |
312
|
|
|
$lines = explode("\n", $export); |
313
|
|
|
foreach ($lines as &$line) { |
314
|
|
|
$line = trim($line); |
315
|
|
|
} |
316
|
|
|
return implode(' ', $lines); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.