Total Complexity | 42 |
Total Lines | 408 |
Duplicated Lines | 0 % |
Changes | 7 | ||
Bugs | 1 | Features | 0 |
Complex classes like Negotiate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Negotiate, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
23 | class Negotiate extends \SimpleSAML\Auth\Source |
||
24 | { |
||
25 | // Constants used in the module |
||
26 | public const STAGEID = '\SimpleSAML\Module\negotiateext\Auth\Source\Negotiate.StageId'; |
||
27 | |||
28 | /** @var \SimpleSAML\Module\ldap\Auth\Ldap */ |
||
29 | protected Ldap $ldap; |
||
30 | |||
31 | /** @var string */ |
||
32 | protected string $backend = ''; |
||
33 | |||
34 | /** @var string */ |
||
35 | protected string $hostname = ''; |
||
36 | |||
37 | /** @var int */ |
||
38 | protected int $port = 389; |
||
39 | |||
40 | /** @var bool */ |
||
41 | protected bool $referrals = true; |
||
42 | |||
43 | /** @var bool */ |
||
44 | protected bool $enableTLS = false; |
||
45 | |||
46 | /** @var bool */ |
||
47 | protected bool $debugLDAP = false; |
||
48 | |||
49 | /** @var int */ |
||
50 | protected int $timeout = 30; |
||
51 | |||
52 | /** @var string */ |
||
53 | protected string $keytab = ''; |
||
54 | |||
55 | /** @var array */ |
||
56 | protected array $base = []; |
||
57 | |||
58 | /** @var array */ |
||
59 | protected array $attr = ['uid']; |
||
60 | |||
61 | /** @var array|null */ |
||
62 | protected ?array $subnet = null; |
||
63 | |||
64 | /** @var string|null */ |
||
65 | protected ?string $admin_user = null; |
||
66 | |||
67 | /** @var string|null */ |
||
68 | protected ?string $admin_pw = null; |
||
69 | |||
70 | /** @var array|null */ |
||
71 | protected ?array $attributes = null; |
||
72 | |||
73 | /** @var array */ |
||
74 | protected array $binaryAttributes = []; |
||
75 | |||
76 | |||
77 | /** |
||
78 | * Constructor for this authentication source. |
||
79 | * |
||
80 | * @param array $info Information about this authentication source. |
||
81 | * @param array $config The configuration of the module |
||
82 | */ |
||
83 | public function __construct(array $info, array $config) |
||
84 | { |
||
85 | // call the parent constructor first, as required by the interface |
||
86 | parent::__construct($info, $config); |
||
87 | |||
88 | $cfg = \SimpleSAML\Configuration::loadFromArray($config); |
||
89 | |||
90 | $this->backend = $cfg->getString('fallback'); |
||
91 | $this->hostname = $cfg->getString('hostname'); |
||
92 | $this->port = $cfg->getInteger('port', 389); |
||
93 | $this->referrals = $cfg->getBoolean('referrals', true); |
||
94 | $this->enableTLS = $cfg->getBoolean('enable_tls', false); |
||
95 | $this->debugLDAP = $cfg->getBoolean('debugLDAP', false); |
||
96 | $this->timeout = $cfg->getInteger('timeout', 30); |
||
97 | $this->base = $cfg->getArrayizeString('base'); |
||
|
|||
98 | $this->attr = $cfg->getArrayizeString('attr', 'uid'); |
||
99 | $this->subnet = $cfg->getArray('subnet', null); |
||
100 | $this->admin_user = $cfg->getString('adminUser', null); |
||
101 | $this->admin_pw = $cfg->getString('adminPassword', null); |
||
102 | $this->attributes = $cfg->getArray('attributes', null); |
||
103 | $this->binaryAttributes = $cfg->getArray('attributes.binary', []); |
||
104 | } |
||
105 | |||
106 | |||
107 | /** |
||
108 | * The inner workings of the module. |
||
109 | * |
||
110 | * Checks to see if client is in the defined subnets (if defined in config). Sends the client a 401 Negotiate and |
||
111 | * responds to the result. If the client fails to provide a proper Kerberos ticket, the login process is handed over |
||
112 | * to the 'fallback' module defined in the config. |
||
113 | * |
||
114 | * LDAP is used as a user metadata source. |
||
115 | * |
||
116 | * @param array &$state Information about the current authentication. |
||
117 | */ |
||
118 | public function authenticate(array &$state): void |
||
164 | } |
||
165 | |||
166 | |||
167 | /** |
||
168 | * @param array $spMetadata |
||
169 | * @return bool |
||
170 | */ |
||
171 | public function spDisabledInMetadata(array $spMetadata): bool |
||
184 | } |
||
185 | |||
186 | |||
187 | /** |
||
188 | * checkMask() looks up the subnet config option and verifies |
||
189 | * that the client is within that range. |
||
190 | * |
||
191 | * Will return TRUE if no subnet option is configured. |
||
192 | * |
||
193 | * @return bool |
||
194 | */ |
||
195 | public function checkMask(): bool |
||
196 | { |
||
197 | // No subnet means all clients are accepted. |
||
198 | if ($this->subnet === null) { |
||
199 | return true; |
||
200 | } |
||
201 | $ip = $_SERVER['REMOTE_ADDR']; |
||
202 | $netUtils = new Utils\Net(); |
||
203 | foreach ($this->subnet as $cidr) { |
||
204 | $ret = $netUtils->ipCIDRcheck($cidr); |
||
205 | if ($ret) { |
||
206 | Logger::debug('Negotiate: Client "' . $ip . '" matched subnet.'); |
||
207 | return true; |
||
208 | } |
||
209 | } |
||
210 | Logger::debug('Negotiate: Client "' . $ip . '" did not match subnet.'); |
||
211 | return false; |
||
212 | } |
||
213 | |||
214 | |||
215 | /** |
||
216 | * Send the actual headers and body of the 401. Embedded in the body is a post that is triggered by JS if the client |
||
217 | * wants to show the 401 message. |
||
218 | * |
||
219 | * @param array $params additional parameters to the URL in the URL in the body. |
||
220 | */ |
||
221 | protected function sendNegotiate(array $params): void |
||
222 | { |
||
223 | $authPage = Module::getModuleURL('negotiateext/auth.php'); |
||
224 | $httpUtils = new Utils\HTTP(); |
||
225 | $httpUtils->redirectTrustedURL($authPage, $params); |
||
226 | } |
||
227 | |||
228 | |||
229 | /** |
||
230 | * Passes control of the login process to a different module. |
||
231 | * |
||
232 | * @param array $state Information about the current authentication. |
||
233 | * |
||
234 | * @throws \SimpleSAML\Error\Error If couldn't determine the auth source. |
||
235 | * @throws \SimpleSAML\Error\Exception |
||
236 | * @throws \Exception |
||
237 | */ |
||
238 | public static function fallBack(array &$state): void |
||
239 | { |
||
240 | $authId = $state['LogoutState']['negotiate:backend']; |
||
241 | |||
242 | if ($authId === null) { |
||
243 | throw new Error\Error([500, "Unable to determine auth source."]); |
||
244 | } |
||
245 | Logger::debug('Negotiate: fallBack to ' . $authId); |
||
246 | $source = Auth\Source::getById($authId); |
||
247 | |||
248 | if ($source === null) { |
||
249 | throw new Exception('Could not find authentication source with id ' . $authId); |
||
250 | } |
||
251 | |||
252 | try { |
||
253 | $source->authenticate($state); |
||
254 | } catch (Error\Exception $e) { |
||
255 | Auth\State::throwException($state, $e); |
||
256 | } catch (Exception $e) { |
||
257 | $e = new Error\UnserializableException($e); |
||
258 | Auth\State::throwException($state, $e); |
||
259 | } |
||
260 | // fallBack never returns after loginCompleted() |
||
261 | Logger::debug('Negotiate: backend returned'); |
||
262 | self::loginCompleted($state); |
||
263 | } |
||
264 | |||
265 | |||
266 | /** |
||
267 | * @param array $state Information about the current authentication. |
||
268 | */ |
||
269 | public function externalAuth(array &$state): void |
||
270 | { |
||
271 | Logger::debug('Negotiate - authenticate(): remote user found'); |
||
272 | $this->ldap = new Ldap( |
||
273 | $this->hostname, |
||
274 | $this->enableTLS, |
||
275 | $this->debugLDAP, |
||
276 | $this->timeout, |
||
277 | $this->port, |
||
278 | $this->referrals |
||
279 | ); |
||
280 | |||
281 | $user = $_SERVER['REMOTE_USER']; |
||
282 | Logger::info('Negotiate - authenticate(): ' . $user . ' authenticated.'); |
||
283 | $lookup = $this->lookupUserData($user); |
||
284 | if ($lookup) { |
||
285 | $state['Attributes'] = $lookup; |
||
286 | // Override the backend so logout will know what to look for |
||
287 | $state['LogoutState'] = [ |
||
288 | 'negotiate:backend' => null, |
||
289 | ]; |
||
290 | Logger::info('Negotiate - authenticate(): ' . $user . ' authorized.'); |
||
291 | Auth\Source::completeAuth($state); |
||
292 | // Never reached. |
||
293 | assert(false); |
||
294 | } |
||
295 | } |
||
296 | |||
297 | |||
298 | /** |
||
299 | * Passes control of the login process to a different module. |
||
300 | * |
||
301 | * @param string $state Information about the current authentication. |
||
302 | * |
||
303 | * @throws \SimpleSAML\Error\BadRequest If couldn't determine the auth source. |
||
304 | * @throws \SimpleSAML\Error\NoState |
||
305 | * @throws \SimpleSAML\Error\Exception |
||
306 | */ |
||
307 | public static function external(): void |
||
308 | { |
||
309 | if (!isset($_REQUEST['AuthState'])) { |
||
310 | throw new Error\BadRequest('Missing "AuthState" parameter.'); |
||
311 | } |
||
312 | Logger::debug('Negotiate: external returned'); |
||
313 | $sid = Auth\State::parseStateID($_REQUEST['AuthState']); |
||
314 | |||
315 | $state = Auth\State::loadState($_REQUEST['AuthState'], self::STAGEID, true); |
||
316 | if ($state === null) { |
||
317 | if ($sid['url'] === null) { |
||
318 | throw new Error\NoState(); |
||
319 | } |
||
320 | $httpUtils = new Utils\HTTP(); |
||
321 | $httpUtils->redirectUntrustedURL($sid['url'], ['negotiateext.auth' => 'false']); |
||
322 | assert(false); |
||
323 | } |
||
324 | |||
325 | Assert::isArray($state); |
||
326 | |||
327 | if (!empty($_SERVER['REMOTE_USER'])) { |
||
328 | $source = Auth\Source::getById($state['negotiate:authId']); |
||
329 | if ($source === null) { |
||
330 | /* |
||
331 | * The only way this should fail is if we remove or rename the authentication source |
||
332 | * while the user is at the login page. |
||
333 | */ |
||
334 | throw new Error\Exception( |
||
335 | 'Could not find authentication source with id ' . $state['negotiate:authId'] |
||
336 | ); |
||
337 | } |
||
338 | /* |
||
339 | * Make sure that we haven't switched the source type while the |
||
340 | * user was at the authentication page. This can only happen if we |
||
341 | * change config/authsources.php while an user is logging in. |
||
342 | */ |
||
343 | if (!($source instanceof self)) { |
||
344 | throw new Error\Exception('Authentication source type changed.'); |
||
345 | } |
||
346 | Logger::debug('Negotiate - authenticate(): looking for Negotate'); |
||
347 | $source->externalAuth($state); |
||
348 | } |
||
349 | |||
350 | self::fallBack($state); |
||
351 | assert(false); |
||
352 | } |
||
353 | |||
354 | |||
355 | /** |
||
356 | * Strips away the realm of the Kerberos identifier, looks up what attributes to fetch from SP metadata and |
||
357 | * searches the directory. |
||
358 | * |
||
359 | * @param string $user The Kerberos user identifier. |
||
360 | * |
||
361 | * @return array|null The attributes for the user or NULL if not found. |
||
362 | */ |
||
363 | protected function lookupUserData(string $user): ?array |
||
380 | } |
||
381 | } |
||
382 | |||
383 | |||
384 | /** |
||
385 | * Elevates the LDAP connection to allow restricted lookups if |
||
386 | * so configured. Does nothing if not. |
||
387 | * |
||
388 | * @throws \SimpleSAML\Error\AuthSource |
||
389 | */ |
||
390 | protected function adminBind(): void |
||
402 | } |
||
403 | } |
||
404 | |||
405 | |||
406 | /** |
||
407 | * Log out from this authentication source. |
||
408 | * |
||
409 | * This method either logs the user out from Negotiate or passes the |
||
410 | * logout call to the fallback module. |
||
411 | * |
||
412 | * @param array &$state Information about the current logout operation. |
||
413 | */ |
||
414 | public function logout(array &$state): void |
||
431 | } |
||
432 | } |
||
433 | } |
||
434 |
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.