Total Complexity | 62 |
Total Lines | 410 |
Duplicated Lines | 0 % |
Changes | 3 | ||
Bugs | 0 | Features | 0 |
Complex classes like Fticks 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 Fticks, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
50 | class Fticks extends Auth\ProcessingFilter |
||
51 | { |
||
52 | /** @var string F-ticks version number */ |
||
53 | private static string $fticksVersion = '1.0'; |
||
54 | |||
55 | /** @var string F-ticks federation identifier */ |
||
56 | private string $federation; |
||
57 | |||
58 | /** @var string A salt to apply when digesting usernames (defaults to config file salt) */ |
||
59 | private string $salt; |
||
60 | |||
61 | /** @var string The logging backend */ |
||
62 | private string $logdest = 'simplesamlphp'; |
||
63 | |||
64 | /** @var array Backend specific logging config */ |
||
65 | private array $logconfig = []; |
||
66 | |||
67 | /** @var string|false The username attribute to use */ |
||
68 | private $userId = false; |
||
69 | |||
70 | /** @var string|false The realm attribute to use */ |
||
71 | private $realm = false; |
||
72 | |||
73 | /** @var string The hashing algorithm to use */ |
||
74 | private string $algorithm = 'sha256'; |
||
75 | |||
76 | /** @var array|false F-ticks attributes to exclude */ |
||
77 | private $exclude = false; |
||
78 | |||
79 | |||
80 | /** |
||
81 | * Log a message to the desired destination |
||
82 | * |
||
83 | * @param string $msg message to log |
||
84 | */ |
||
85 | private function log(string $msg): void |
||
143 | } |
||
144 | } |
||
145 | |||
146 | |||
147 | /** |
||
148 | * Generate a PN hash |
||
149 | * |
||
150 | * @param array $state |
||
151 | * @return string|false $hash |
||
152 | */ |
||
153 | private function generatePNhash(array &$state) |
||
185 | } |
||
186 | |||
187 | |||
188 | /** |
||
189 | * Escape F-ticks values |
||
190 | * |
||
191 | * value = 1*( ALPHA / DIGIT / '_' / '-' / ':' / '.' / ',' / ';') |
||
192 | * ... but add a / for entityIDs |
||
193 | * |
||
194 | * @param string $value |
||
195 | * @return string $value |
||
196 | */ |
||
197 | private function escapeFticks(string $value): string |
||
200 | } |
||
201 | |||
202 | |||
203 | /** |
||
204 | * Initialize this filter, parse configuration. |
||
205 | * |
||
206 | * @param array $config Configuration information about this filter. |
||
207 | * @param mixed $reserved For future use. |
||
208 | * @throws \SimpleSAML\Error\Exception |
||
209 | */ |
||
210 | public function __construct(array $config, $reserved) |
||
211 | { |
||
212 | parent::__construct($config, $reserved); |
||
213 | |||
214 | if (array_key_exists('federation', $config)) { |
||
215 | if (is_string($config['federation'])) { |
||
216 | $this->federation = $config['federation']; |
||
217 | } else { |
||
218 | throw new Error\Exception('Federation identifier must be a string'); |
||
219 | } |
||
220 | } else { |
||
221 | throw new Error\Exception('Federation identifier must be set'); |
||
222 | } |
||
223 | |||
224 | if (array_key_exists('salt', $config)) { |
||
225 | if (is_string($config['salt'])) { |
||
226 | $this->salt = $config['salt']; |
||
227 | } else { |
||
228 | throw new Error\Exception('Salt must be a string'); |
||
229 | } |
||
230 | } else { |
||
231 | $configUtils = new Utils\Config(); |
||
232 | $this->salt = $configUtils->getSecretSalt(); |
||
233 | } |
||
234 | |||
235 | if (array_key_exists('userId', $config)) { |
||
236 | if (is_string($config['userId'])) { |
||
237 | $this->userId = $config['userId']; |
||
238 | } else { |
||
239 | throw new Error\Exception('UserId must be a string'); |
||
240 | } |
||
241 | } |
||
242 | |||
243 | if (array_key_exists('realm', $config)) { |
||
244 | if (is_string($config['realm'])) { |
||
245 | $this->realm = $config['realm']; |
||
246 | } else { |
||
247 | throw new Error\Exception('realm must be a string'); |
||
248 | } |
||
249 | } |
||
250 | |||
251 | if (array_key_exists('algorithm', $config)) { |
||
252 | if ( |
||
253 | is_string($config['algorithm']) |
||
254 | && in_array($config['algorithm'], hash_algos()) |
||
255 | ) { |
||
256 | $this->algorithm = $config['algorithm']; |
||
257 | } else { |
||
258 | throw new Error\Exception('algorithm must be a hash algorithm listed in hash_algos()'); |
||
259 | } |
||
260 | } |
||
261 | |||
262 | if (array_key_exists('exclude', $config)) { |
||
263 | if (is_array($config['exclude'])) { |
||
264 | $this->exclude = $config['exclude']; |
||
265 | } elseif (is_string($config['exclude'])) { |
||
266 | $this->exclude = [$config['exclude']]; |
||
267 | } else { |
||
268 | throw new Error\Exception('F-ticks exclude must be an array'); |
||
269 | } |
||
270 | } |
||
271 | |||
272 | if (array_key_exists('logdest', $config)) { |
||
273 | if ( |
||
274 | is_string($config['logdest']) && |
||
275 | in_array($config['logdest'], ['local', 'syslog', 'remote', 'stdout', 'errorlog', 'simplesamlphp']) |
||
276 | ) { |
||
277 | $this->logdest = $config['logdest']; |
||
278 | } else { |
||
279 | throw new Error\Exception( |
||
280 | 'F-ticks log destination must be one of [local, remote, stdout, errorlog, simplesamlphp]' |
||
281 | ); |
||
282 | } |
||
283 | } |
||
284 | |||
285 | /* match SSP config or we risk mucking up the openlog call */ |
||
286 | $globalConfig = Configuration::getInstance(); |
||
287 | $defaultFacility = $globalConfig->getInteger( |
||
288 | 'logging.facility', |
||
289 | defined('LOG_LOCAL5') ? constant('LOG_LOCAL5') : LOG_USER |
||
290 | ); |
||
291 | $defaultProcessName = $globalConfig->getString('logging.processname', 'SimpleSAMLphp'); |
||
292 | if (array_key_exists('logconfig', $config)) { |
||
293 | if (is_array($config['logconfig'])) { |
||
294 | $this->logconfig = $config['logconfig']; |
||
295 | } else { |
||
296 | throw new Error\Exception('F-ticks logconfig must be an array'); |
||
297 | } |
||
298 | } else { |
||
299 | $this->logconfig['facility'] = $defaultFacility; |
||
300 | $this->logconfig['processname'] = $defaultProcessName; |
||
301 | } |
||
302 | |||
303 | /* warn if we risk mucking up the openlog call (doesn't matter for remote syslog) */ |
||
304 | if (in_array($this->logdest, ['local', 'syslog'])) { |
||
305 | $this->warnRiskyLogSettings($defaultFacility, $defaultProcessName); |
||
306 | } |
||
307 | } |
||
308 | |||
309 | |||
310 | /** |
||
311 | * Warn about risky logger settings |
||
312 | * |
||
313 | * @param string $defaultFacility |
||
314 | * @param string $defaultProcessName |
||
315 | * @return void |
||
316 | */ |
||
317 | private function warnRiskyLogSettings(string $defaultFacility, string $defaultProcessName): void |
||
318 | { |
||
319 | if ( |
||
320 | array_key_exists('facility', $this->logconfig) |
||
321 | && ($this->logconfig['facility'] !== $defaultFacility) |
||
322 | ) { |
||
323 | Logger::warning( |
||
324 | 'F-ticks syslog facility differs from global config which may cause' |
||
325 | . ' SimpleSAMLphp\'s logging to behave inconsistently' |
||
326 | ); |
||
327 | } |
||
328 | if ( |
||
329 | array_key_exists('processname', $this->logconfig) |
||
330 | && ($this->logconfig['processname'] !== $defaultProcessName) |
||
331 | ) { |
||
332 | Logger::warning( |
||
333 | 'F-ticks syslog processname differs from global config which may cause' |
||
334 | . ' SimpleSAMLphp\'s logging to behave inconsistently' |
||
335 | ); |
||
336 | } |
||
337 | } |
||
338 | |||
339 | |||
340 | /** |
||
341 | * Process this filter |
||
342 | * |
||
343 | * @param mixed &$state |
||
344 | */ |
||
345 | public function process(array &$state): void |
||
346 | { |
||
347 | Assert::keyExists($state, 'Destination'); |
||
348 | Assert::keyExists($state['Destination'], 'entityid'); |
||
349 | Assert::keyExists($state, 'Source'); |
||
350 | Assert::keyExists($state['Source'], 'entityid'); |
||
351 | |||
352 | $fticks = []; |
||
353 | |||
354 | /* AFAIK the AuthProc will only execute if there is prior success */ |
||
355 | $fticks['RESULT'] = 'OK'; |
||
356 | |||
357 | /* SAML IdP entity Id */ |
||
358 | if (array_key_exists('saml:sp:IdP', $state)) { |
||
359 | $fticks['AP'] = $state['saml:sp:IdP']; |
||
360 | } else { |
||
361 | $fticks['AP'] = $state['Source']['entityid']; |
||
362 | } |
||
363 | |||
364 | /* SAML SP entity Id */ |
||
365 | $fticks['RP'] = $state['Destination']['entityid']; |
||
366 | |||
367 | /* SAML session id */ |
||
368 | $session = Session::getSessionFromRequest(); |
||
369 | $fticks['CSI'] = $session->getTrackID(); |
||
370 | |||
371 | /* Authentication method identifier */ |
||
372 | if ( |
||
373 | array_key_exists('saml:sp:State', $state) |
||
374 | && array_key_exists('saml:sp:AuthnContext', $state['saml:sp:State']) |
||
375 | ) { |
||
376 | $fticks['AM'] = $state['saml:sp:State']['saml:sp:AuthnContext']; |
||
377 | } elseif ( |
||
378 | array_key_exists('SimpleSAML_Auth_State.stage', $state) |
||
379 | && preg_match('/UserPass/', $state['SimpleSAML_Auth_State.stage']) |
||
380 | ) { |
||
381 | /* hack to try identify LDAP et al as Password */ |
||
382 | $fticks['AM'] = Constants::AC_PASSWORD; |
||
383 | } |
||
384 | |||
385 | /* ePTID */ |
||
386 | $pn = $this->generatePNhash($state); |
||
387 | if ($pn !== false) { |
||
388 | $fticks['PN'] = $pn; |
||
389 | } |
||
390 | |||
391 | /* timestamp */ |
||
392 | if ( |
||
393 | array_key_exists('saml:sp:State', $state) |
||
394 | && array_key_exists('saml:AuthnInstant', $state['saml:sp:State']) |
||
395 | ) { |
||
396 | $fticks['TS'] = $state['saml:sp:State']['saml:AuthnInstant']; |
||
397 | } else { |
||
398 | $fticks['TS'] = time(); |
||
399 | } |
||
400 | |||
401 | /* realm */ |
||
402 | if ($this->realm !== false) { |
||
403 | Assert::keyExists($state, 'Attributes'); |
||
404 | if (array_key_exists($this->realm, $state['Attributes'])) { |
||
405 | if (is_array($state['Attributes'][$this->realm])) { |
||
406 | $fticks['REALM'] = $state['Attributes'][$this->realm][0]; |
||
407 | } else { |
||
408 | $fticks['REALM'] = $state['Attributes'][$this->realm]; |
||
409 | } |
||
410 | } |
||
411 | } |
||
412 | |||
413 | /* allow some attributes to be excluded */ |
||
414 | if ($this->exclude !== false) { |
||
415 | $fticks = array_filter($fticks, [$this, 'filterExcludedAttributes'], ARRAY_FILTER_USE_KEY); |
||
416 | } |
||
417 | |||
418 | /* assemble an F-ticks log string */ |
||
419 | $this->log($this->assembleFticksLogString($fticks)); |
||
420 | } |
||
421 | |||
422 | |||
423 | /** |
||
424 | * Callback method to filter excluded attributes |
||
425 | * |
||
426 | * @param string $attr |
||
427 | * @return bool |
||
428 | */ |
||
429 | private function filterExcludedAttributes(string $attr): bool |
||
430 | { |
||
431 | return !in_array($attr, $this->exclude); |
||
432 | } |
||
433 | |||
434 | |||
435 | /** |
||
436 | * Assemble fticks log string |
||
437 | * |
||
438 | * @param array $fticks |
||
439 | * @return string |
||
440 | */ |
||
441 | private function assembleFticksLogString(array $fticks): string |
||
460 | } |
||
461 | } |
||
462 |