Total Complexity | 46 |
Total Lines | 480 |
Duplicated Lines | 0 % |
Changes | 17 | ||
Bugs | 0 | Features | 0 |
Complex classes like ADFS 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 ADFS, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
48 | class ADFS |
||
49 | { |
||
50 | /** |
||
51 | * @param \SimpleSAML\IdP $idp |
||
52 | * @throws \SimpleSAML\Error\MetadataNotFound |
||
53 | */ |
||
54 | public static function receiveAuthnRequest(Request $request, IdP $idp): StreamedResponse |
||
55 | { |
||
56 | parse_str($request->server->get('QUERY_STRING'), $query); |
||
57 | |||
58 | $requestid = $query['wctx'] ?? null; |
||
59 | $issuer = $query['wtrealm']; |
||
60 | |||
61 | $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); |
||
|
|||
62 | $spMetadata = $metadata->getMetaDataConfig($issuer, 'adfs-sp-remote'); |
||
63 | |||
64 | Logger::info('ADFS - IdP.prp: Incoming Authentication request: ' . $issuer . ' id ' . $requestid); |
||
65 | |||
66 | $username = null; |
||
67 | if ($request->query->has('username')) { |
||
68 | $username = (string) $request->query->get('username'); |
||
69 | } |
||
70 | |||
71 | $state = [ |
||
72 | 'Responder' => [ADFS::class, 'sendResponse'], |
||
73 | 'SPMetadata' => $spMetadata->toArray(), |
||
74 | 'ForceAuthn' => false, |
||
75 | 'isPassive' => false, |
||
76 | 'adfs:wctx' => $requestid, |
||
77 | 'adfs:wreply' => false, |
||
78 | ]; |
||
79 | |||
80 | if ($username !== null) { |
||
81 | $state['core:username'] = $username; |
||
82 | } |
||
83 | |||
84 | if (isset($query['wreply']) && !empty($query['wreply'])) { |
||
85 | $httpUtils = new Utils\HTTP(); |
||
86 | $state['adfs:wreply'] = $httpUtils->checkURLAllowed($query['wreply']); |
||
87 | } |
||
88 | |||
89 | return new StreamedResponse( |
||
90 | function () use ($idp, &$state) { |
||
91 | $idp->handleAuthenticationRequest($state); |
||
92 | }, |
||
93 | ); |
||
94 | } |
||
95 | |||
96 | |||
97 | /** |
||
98 | * @param string $issuer |
||
99 | * @param string $target |
||
100 | * @param string $nameid |
||
101 | * @param array $attributes |
||
102 | * @param int $assertionLifetime |
||
103 | * @return \SimpleSAML\SAML11\XML\saml\Assertion |
||
104 | */ |
||
105 | private static function generateAssertion( |
||
106 | string $issuer, |
||
107 | string $target, |
||
108 | string $nameid, |
||
109 | array $attributes, |
||
110 | int $assertionLifetime, |
||
111 | ): Assertion { |
||
112 | $httpUtils = new Utils\HTTP(); |
||
113 | $randomUtils = new Utils\Random(); |
||
114 | $timeUtils = new Utils\Time(); |
||
115 | |||
116 | $issueInstant = $timeUtils->generateTimestamp(); |
||
117 | $notBefore = DateInterval::createFromDateString('30 seconds'); |
||
118 | $notOnOrAfter = DateInterval::createFromDateString(sprintf('%d seconds', $assertionLifetime)); |
||
119 | $assertionID = $randomUtils->generateID(); |
||
120 | $nameidFormat = 'http://schemas.xmlsoap.org/claims/UPN'; |
||
121 | $nameid = htmlspecialchars($nameid); |
||
122 | $now = new DateTimeImmutable('now', new DateTimeZone('Z')); |
||
123 | |||
124 | if ($httpUtils->isHTTPS()) { |
||
125 | $method = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'; |
||
126 | } else { |
||
127 | $method = C::AC_PASSWORD; |
||
128 | } |
||
129 | |||
130 | $audience = new Audience($target); |
||
131 | $audienceRestrictionCondition = new AudienceRestrictionCondition([$audience]); |
||
132 | $conditions = new Conditions( |
||
133 | [$audienceRestrictionCondition], |
||
134 | [], |
||
135 | [], |
||
136 | $now->sub($notBefore), |
||
137 | $now->add($notOnOrAfter), |
||
138 | ); |
||
139 | |||
140 | $nameIdentifier = new NameIdentifier($nameid, null, $nameidFormat); |
||
141 | $subject = new Subject(null, $nameIdentifier); |
||
142 | |||
143 | $authenticationStatement = new AuthenticationStatement($subject, $method, $now); |
||
144 | |||
145 | $attrs = []; |
||
146 | $attrUtils = new Utils\Attributes(); |
||
147 | foreach ($attributes as $name => $values) { |
||
148 | if ((!is_array($values)) || (count($values) == 0)) { |
||
149 | continue; |
||
150 | } |
||
151 | |||
152 | list($namespace, $name) = $attrUtils->getAttributeNamespace( |
||
153 | $name, |
||
154 | 'http://schemas.xmlsoap.org/claims', |
||
155 | ); |
||
156 | |||
157 | $namespace = htmlspecialchars($namespace); |
||
158 | $name = htmlspecialchars($name); |
||
159 | $attrValue = []; |
||
160 | foreach ($values as $value) { |
||
161 | if ((!isset($value)) || ($value === '')) { |
||
162 | continue; |
||
163 | } |
||
164 | $attrValue[] = new AttributeValue($value); |
||
165 | } |
||
166 | $attrs[] = new Attribute($name, $namespace, $attrValue); |
||
167 | } |
||
168 | $attributeStatement = new AttributeStatement($subject, $attrs); |
||
169 | |||
170 | return new Assertion( |
||
171 | $assertionID, |
||
172 | $issuer, |
||
173 | $now, |
||
174 | $conditions, |
||
175 | null, // Advice |
||
176 | [$authenticationStatement, $attributeStatement], |
||
177 | ); |
||
178 | } |
||
179 | |||
180 | |||
181 | /** |
||
182 | * @param \SimpleSAML\SAML11\XML\saml\Assertion $assertion |
||
183 | * @param string $key |
||
184 | * @param string $cert |
||
185 | * @param string $algo |
||
186 | * @param string|null $passphrase |
||
187 | * @return \SimpleSAML\SAML11\XML\saml\Assertion |
||
188 | */ |
||
189 | private static function signAssertion( |
||
190 | Assertion $assertion, |
||
191 | string $key, |
||
192 | string $cert, |
||
193 | string $algo, |
||
194 | #[\SensitiveParameter] |
||
195 | string $passphrase = null, |
||
196 | ): Assertion { |
||
197 | $key = PrivateKey::fromFile($key, $passphrase); |
||
198 | $pubkey = PublicKey::fromFile($cert); |
||
199 | $keyInfo = new KeyInfo([ |
||
200 | new X509Data( |
||
201 | [new X509Certificate( |
||
202 | trim(chunk_split(base64_encode($pubkey->getPEM()->data()))), |
||
203 | )], |
||
204 | ), |
||
205 | ]); |
||
206 | |||
207 | $signer = (new SignatureAlgorithmFactory())->getAlgorithm( |
||
208 | $algo, |
||
209 | $key, |
||
210 | ); |
||
211 | |||
212 | $assertion->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo); |
||
213 | return $assertion; |
||
214 | } |
||
215 | |||
216 | |||
217 | /** |
||
218 | * @param string $wreply |
||
219 | * @param string $wresult |
||
220 | * @param ?string $wctx |
||
221 | */ |
||
222 | private static function postResponse(string $wreply, string $wresult, ?string $wctx): void |
||
232 | } |
||
233 | |||
234 | |||
235 | /** |
||
236 | * Get the metadata of a given hosted ADFS IdP. |
||
237 | * |
||
238 | * @param string $entityid The entity ID of the hosted ADFS IdP whose metadata we want to fetch. |
||
239 | * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler Optionally the metadata storage to use, |
||
240 | * if omitted the configured handler will be used. |
||
241 | * @return array |
||
242 | * |
||
243 | * @throws \SimpleSAML\Error\Exception |
||
244 | * @throws \SimpleSAML\Error\MetadataNotFound |
||
245 | */ |
||
246 | public static function getHostedMetadata(string $entityid, MetaDataStorageHandler $handler = null): array |
||
396 | } |
||
397 | |||
398 | |||
399 | /** |
||
400 | * @param array $state |
||
401 | * @throws \Exception |
||
402 | */ |
||
403 | public static function sendResponse(array $state): void |
||
404 | { |
||
405 | $spMetadata = $state["SPMetadata"]; |
||
406 | $spEntityId = $spMetadata['entityid']; |
||
407 | $spMetadata = Configuration::loadFromArray( |
||
408 | $spMetadata, |
||
409 | '$metadata[' . var_export($spEntityId, true) . ']', |
||
410 | ); |
||
411 | |||
412 | $attributes = $state['Attributes']; |
||
413 | |||
414 | $nameidattribute = $spMetadata->getValue('simplesaml.nameidattribute'); |
||
415 | if (!empty($nameidattribute)) { |
||
416 | if (!array_key_exists($nameidattribute, $attributes)) { |
||
417 | throw new Exception('simplesaml.nameidattribute does not exist in resulting attribute set'); |
||
418 | } |
||
419 | $nameid = $attributes[$nameidattribute][0]; |
||
420 | } else { |
||
421 | $randomUtils = new Utils\Random(); |
||
422 | $nameid = $randomUtils->generateID(); |
||
423 | } |
||
424 | |||
425 | $idp = IdP::getByState($state); |
||
426 | $idpMetadata = $idp->getConfig(); |
||
427 | $idpEntityId = $idpMetadata->getString('entityid'); |
||
428 | |||
429 | $idp->addAssociation([ |
||
430 | 'id' => 'adfs:' . $spEntityId, |
||
431 | 'Handler' => ADFS::class, |
||
432 | 'adfs:entityID' => $spEntityId, |
||
433 | ]); |
||
434 | |||
435 | $assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null); |
||
436 | if ($assertionLifetime === null) { |
||
437 | $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300); |
||
438 | } |
||
439 | |||
440 | $assertion = ADFS::generateAssertion($idpEntityId, $spEntityId, $nameid, $attributes, $assertionLifetime); |
||
441 | |||
442 | $privateKeyCfg = $idpMetadata->getOptionalString('privatekey', null); |
||
443 | $certificateCfg = $idpMetadata->getOptionalString('certificate', null); |
||
444 | |||
445 | if ($privateKeyCfg !== null && $certificateCfg !== null) { |
||
446 | $configUtils = new Utils\Config(); |
||
447 | $privateKeyFile = $configUtils->getCertPath($privateKeyCfg); |
||
448 | $certificateFile = $configUtils->getCertPath($certificateCfg); |
||
449 | $passphrase = $idpMetadata->getOptionalString('privatekey_pass', null); |
||
450 | |||
451 | $algo = $spMetadata->getOptionalString('signature.algorithm', null); |
||
452 | if ($algo === null) { |
||
453 | $algo = $idpMetadata->getOptionalString('signature.algorithm', C::SIG_RSA_SHA256); |
||
454 | } |
||
455 | |||
456 | $assertion = ADFS::signAssertion($assertion, $privateKeyFile, $certificateFile, $algo, $passphrase); |
||
457 | $assertion = Assertion::fromXML($assertion->toXML()); |
||
458 | } |
||
459 | |||
460 | $requestSecurityToken = new RequestSecurityToken(null, [$assertion]); |
||
461 | $appliesTo = new AppliesTo([new EndpointReference(new Address($spEntityId))]); |
||
462 | $requestSecurityTokenResponse = new RequestSecurityTokenResponse(null, [$requestSecurityToken, $appliesTo]); |
||
463 | |||
464 | $xmlResponse = $requestSecurityTokenResponse->toXML(); |
||
465 | $wresult = $xmlResponse->ownerDocument->saveXML($xmlResponse); |
||
466 | $wctx = $state['adfs:wctx']; |
||
467 | $wreply = $state['adfs:wreply'] ? : $spMetadata->getValue('prp'); |
||
468 | ADFS::postResponse($wreply, $wresult, $wctx); |
||
469 | } |
||
470 | |||
471 | |||
472 | /** |
||
473 | * @param \SimpleSAML\IdP $idp |
||
474 | * @param array $state |
||
475 | */ |
||
476 | public static function sendLogoutResponse(IdP $idp, array $state): void |
||
483 | ); |
||
484 | } |
||
485 | |||
486 | |||
487 | /** |
||
488 | * @param \SimpleSAML\IdP $idp |
||
489 | * @throws \Exception |
||
490 | */ |
||
491 | public static function receiveLogoutMessage(IdP $idp): void |
||
509 | } |
||
510 | |||
511 | |||
512 | /** |
||
513 | * accepts an association array, and returns a URL that can be accessed to terminate the association |
||
514 | * |
||
515 | * @param \SimpleSAML\IdP $idp |
||
516 | * @param array $association |
||
517 | * @param string $relayState |
||
518 | * @return string |
||
519 | */ |
||
520 | public static function getLogoutURL(IdP $idp, array $association, string $relayState): string |
||
530 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.