1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace SimpleSAML\Module\adfs\IdP; |
||||||
6 | |||||||
7 | use DateInterval; |
||||||
8 | use DateTimeImmutable; |
||||||
9 | use DateTimeZone; |
||||||
10 | use Exception; |
||||||
11 | use SimpleSAML\Assert\Assert; |
||||||
12 | use SimpleSAML\Configuration; |
||||||
13 | use SimpleSAML\Error; |
||||||
14 | use SimpleSAML\IdP; |
||||||
15 | use SimpleSAML\Logger; |
||||||
16 | use SimpleSAML\Metadata\MetaDataStorageHandler; |
||||||
17 | use SimpleSAML\Module; |
||||||
18 | use SimpleSAML\SAML2\Constants as SAML2_C; |
||||||
19 | use SimpleSAML\SAML11\Constants as C; |
||||||
20 | use SimpleSAML\SAML11\XML\saml\Assertion; |
||||||
21 | use SimpleSAML\SAML11\XML\saml\Attribute; |
||||||
22 | use SimpleSAML\SAML11\XML\saml\AttributeStatement; |
||||||
23 | use SimpleSAML\SAML11\XML\saml\AttributeValue; |
||||||
24 | use SimpleSAML\SAML11\XML\saml\Audience; |
||||||
25 | use SimpleSAML\SAML11\XML\saml\AudienceRestrictionCondition; |
||||||
26 | use SimpleSAML\SAML11\XML\saml\AuthenticationStatement; |
||||||
27 | use SimpleSAML\SAML11\XML\saml\Conditions; |
||||||
28 | use SimpleSAML\SAML11\XML\saml\NameIdentifier; |
||||||
29 | use SimpleSAML\SAML11\XML\saml\{ConfirmationMethod, Subject, SubjectConfirmation}; |
||||||
30 | use SimpleSAML\SOAP\Constants as SOAP_C; |
||||||
31 | use SimpleSAML\SOAP\XML\env_200305\{Body, Envelope, Header}; |
||||||
32 | use SimpleSAML\Utils; |
||||||
33 | use SimpleSAML\WSSecurity\XML\wsa_200508\{Action, Address, EndpointReference, MessageID, RelatesTo, To}; |
||||||
34 | use SimpleSAML\WSSecurity\XML\wsp\AppliesTo; |
||||||
35 | use SimpleSAML\WSSecurity\XML\wsse\{KeyIdentifier, Password, Security, SecurityTokenReference, Username, UsernameToken}; |
||||||
36 | use SimpleSAML\WSSecurity\XML\wst_200502\{KeyType, KeyTypeEnum, Lifetime, RequestType, RequestTypeEnum, TokenType}; |
||||||
37 | use SimpleSAML\WSSecurity\XML\wst_200502\{RequestedSecurityToken, RequestSecurityToken, RequestSecurityTokenResponse}; |
||||||
38 | use SimpleSAML\WSSecurity\XML\wst_200502\{RequestedAttachedReference, RequestedUnattachedReference}; |
||||||
39 | use SimpleSAML\WSSecurity\XML\wsu\{Created, Expires, Timestamp}; |
||||||
40 | use SimpleSAML\XHTML\Template; |
||||||
41 | use SimpleSAML\XML\Attribute as XMLAttribute; |
||||||
42 | use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory; |
||||||
43 | use SimpleSAML\XMLSecurity\Key\PrivateKey; |
||||||
44 | use SimpleSAML\XMLSecurity\Key\X509Certificate as PublicKey; |
||||||
45 | use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; |
||||||
46 | use SimpleSAML\XMLSecurity\XML\ds\X509Certificate; |
||||||
47 | use SimpleSAML\XMLSecurity\XML\ds\X509Data; |
||||||
48 | use Symfony\Component\HttpFoundation\{Request, StreamedResponse}; |
||||||
49 | |||||||
50 | use function array_pop; |
||||||
51 | use function base64_encode; |
||||||
52 | use function chunk_split; |
||||||
53 | use function str_replace; |
||||||
54 | use function trim; |
||||||
55 | |||||||
56 | class ADFS |
||||||
57 | { |
||||||
58 | /** |
||||||
59 | * @param \Symfony\Component\HttpFoundation\Request $request |
||||||
60 | * @param \SimpleSAML\SOAP\XML\env_200305\Envelope $soapEnvelope |
||||||
61 | * @param \SimpleSAML\Module\adfs\IdP\PassiveIdP $idp |
||||||
62 | * @throws \SimpleSAML\Error\MetadataNotFound |
||||||
63 | */ |
||||||
64 | public static function receivePassiveAuthnRequest( |
||||||
65 | Request $request, |
||||||
66 | Envelope $soapEnvelope, |
||||||
67 | PassiveIdP $idp, |
||||||
68 | ): StreamedResponse { |
||||||
69 | // Parse the SOAP-header |
||||||
70 | $header = $soapEnvelope->getHeader(); |
||||||
71 | |||||||
72 | $to = To::getChildrenOfClass($header->toXML()); |
||||||
73 | Assert::count($to, 1, 'Missing To in SOAP Header.'); |
||||||
74 | $to = array_pop($to); |
||||||
75 | |||||||
76 | $action = Action::getChildrenOfClass($header->toXML()); |
||||||
77 | Assert::count($action, 1, 'Missing Action in SOAP Header.'); |
||||||
78 | $action = array_pop($action); |
||||||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||||||
79 | |||||||
80 | $messageid = MessageID::getChildrenOfClass($header->toXML()); |
||||||
81 | Assert::count($messageid, 1, 'Missing MessageID in SOAP Header.'); |
||||||
82 | $messageid = array_pop($messageid); |
||||||
83 | |||||||
84 | $security = Security::getChildrenOfClass($header->toXML()); |
||||||
85 | Assert::count($security, 1, 'Missing Security in SOAP Header.'); |
||||||
86 | $security = array_pop($security); |
||||||
87 | |||||||
88 | // Parse the SOAP-body |
||||||
89 | $body = $soapEnvelope->getBody(); |
||||||
90 | |||||||
91 | $requestSecurityToken = RequestSecurityToken::getChildrenOfClass($body->toXML()); |
||||||
92 | Assert::count($requestSecurityToken, 1, 'Missing RequestSecurityToken in SOAP Body.'); |
||||||
93 | $requestSecurityToken = array_pop($requestSecurityToken); |
||||||
94 | |||||||
95 | $appliesTo = AppliesTo::getChildrenOfClass($requestSecurityToken->toXML()); |
||||||
96 | Assert::count($appliesTo, 1, 'Missing AppliesTo in RequestSecurityToken.'); |
||||||
97 | $appliesTo = array_pop($appliesTo); |
||||||
98 | |||||||
99 | $endpointReference = EndpointReference::getChildrenOfClass($appliesTo->toXML()); |
||||||
100 | Assert::count($endpointReference, 1, 'Missing EndpointReference in AppliesTo.'); |
||||||
101 | $endpointReference = array_pop($endpointReference); |
||||||
102 | |||||||
103 | // Make sure the message was addressed to us. |
||||||
104 | if ($to === null || $request->server->get('SCRIPT_URI') !== $to->getContent()) { |
||||||
105 | throw new Error\BadRequest('This server is not the audience for the message received.'); |
||||||
106 | } |
||||||
107 | |||||||
108 | // Ensure we know the issuer |
||||||
109 | $issuer = $endpointReference->getAddress()->getContent(); |
||||||
110 | |||||||
111 | $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); |
||||||
0 ignored issues
–
show
The call to
SimpleSAML\Metadata\Meta...r::getMetadataHandler() has too many arguments starting with SimpleSAML\Configuration::getInstance() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||||
112 | $spMetadata = $metadata->getMetaDataConfig($issuer, 'adfs-sp-remote'); |
||||||
113 | |||||||
114 | $usernameToken = UsernameToken::getChildrenOfClass($security->toXML()); |
||||||
115 | Assert::count($usernameToken, 1, 'Missing UsernameToken in Security.'); |
||||||
116 | $usernameToken = array_pop($usernameToken); |
||||||
117 | |||||||
118 | $username = $usernameToken->getUsername(); |
||||||
119 | $password = Password::getChildrenOfClass($usernameToken->toXML()); |
||||||
120 | $password = array_pop($password); |
||||||
121 | |||||||
122 | if ($username === null || $password === null) { |
||||||
123 | throw new Error\BadRequest('Missing username or password in SOAP header.'); |
||||||
124 | } else { |
||||||
125 | $_SERVER['PHP_AUTH_USER'] = $username->getContent(); |
||||||
126 | $_SERVER['PHP_AUTH_PW'] = $password->getContent(); |
||||||
127 | } |
||||||
128 | |||||||
129 | $requestSecurityTokenStr = $requestSecurityToken->toXML()->ownerDocument->saveXML(); |
||||||
130 | $requestSecurityTokenStr = str_replace($password->getContent(), '*****', $requestSecurityTokenStr); |
||||||
131 | Logger::debug($requestSecurityTokenStr); |
||||||
132 | |||||||
133 | $state = [ |
||||||
134 | 'Responder' => [ADFS::class, 'sendPassiveResponse'], |
||||||
135 | 'SPMetadata' => $spMetadata->toArray(), |
||||||
136 | 'MessageID' => $messageid->getContent(), |
||||||
137 | // Dirty hack to leverage the SAML ECP logics |
||||||
138 | 'saml:Binding' => SAML2_C::BINDING_PAOS, |
||||||
139 | ]; |
||||||
140 | |||||||
141 | return new StreamedResponse( |
||||||
142 | function () use ($idp, &$state) { |
||||||
143 | $idp->handleAuthenticationRequest($state); |
||||||
144 | }, |
||||||
145 | ); |
||||||
146 | } |
||||||
147 | |||||||
148 | |||||||
149 | /** |
||||||
150 | * @param \Symfony\Component\HttpFoundation\Request $request |
||||||
151 | * @param \SimpleSAML\IdP $idp |
||||||
152 | * @throws \SimpleSAML\Error\MetadataNotFound |
||||||
153 | */ |
||||||
154 | public static function receiveAuthnRequest(Request $request, IdP $idp): StreamedResponse |
||||||
155 | { |
||||||
156 | parse_str($request->server->get('QUERY_STRING'), $query); |
||||||
157 | |||||||
158 | $requestid = $query['wctx'] ?? null; |
||||||
159 | $issuer = $query['wtrealm']; |
||||||
160 | |||||||
161 | $metadata = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance()); |
||||||
0 ignored issues
–
show
The call to
SimpleSAML\Metadata\Meta...r::getMetadataHandler() has too many arguments starting with SimpleSAML\Configuration::getInstance() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
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. ![]() |
|||||||
162 | $spMetadata = $metadata->getMetaDataConfig($issuer, 'adfs-sp-remote'); |
||||||
163 | |||||||
164 | Logger::info('ADFS - IdP.prp: Incoming Authentication request: ' . $issuer . ' id ' . $requestid); |
||||||
165 | |||||||
166 | $username = null; |
||||||
167 | if ($request->query->has('username')) { |
||||||
168 | $username = (string) $request->query->get('username'); |
||||||
169 | } |
||||||
170 | |||||||
171 | $wauth = null; |
||||||
172 | if ($request->query->has('wauth')) { |
||||||
173 | $wauth = (string) $request->query->get('wauth'); |
||||||
174 | } |
||||||
175 | |||||||
176 | $state = [ |
||||||
177 | 'Responder' => [ADFS::class, 'sendResponse'], |
||||||
178 | 'SPMetadata' => $spMetadata->toArray(), |
||||||
179 | 'ForceAuthn' => false, |
||||||
180 | 'isPassive' => false, |
||||||
181 | 'adfs:wctx' => $requestid, |
||||||
182 | 'adfs:wreply' => false, |
||||||
183 | ]; |
||||||
184 | |||||||
185 | if ($username !== null) { |
||||||
186 | $state['core:username'] = $username; |
||||||
187 | } |
||||||
188 | |||||||
189 | if ($wauth !== null) { |
||||||
190 | $state['saml:RequestedAuthnContext'] = ['AuthnContextClassRef' => [$wauth]]; |
||||||
191 | } |
||||||
192 | |||||||
193 | if (isset($query['wreply']) && !empty($query['wreply'])) { |
||||||
194 | $httpUtils = new Utils\HTTP(); |
||||||
195 | $state['adfs:wreply'] = $httpUtils->checkURLAllowed($query['wreply']); |
||||||
196 | } |
||||||
197 | |||||||
198 | return new StreamedResponse( |
||||||
199 | function () use ($idp, &$state) { |
||||||
200 | $idp->handleAuthenticationRequest($state); |
||||||
201 | }, |
||||||
202 | ); |
||||||
203 | } |
||||||
204 | |||||||
205 | |||||||
206 | /** |
||||||
207 | * @param string $issuer |
||||||
208 | * @param string $target |
||||||
209 | * @param string $nameid |
||||||
210 | * @param array $attributes |
||||||
211 | * @param int $assertionLifetime |
||||||
212 | * @param string $method |
||||||
213 | * @return \SimpleSAML\SAML11\XML\saml\Assertion |
||||||
214 | */ |
||||||
215 | private static function generateActiveAssertion( |
||||||
216 | string $issuer, |
||||||
217 | string $target, |
||||||
218 | string $nameid, |
||||||
219 | array $attributes, |
||||||
220 | int $assertionLifetime, |
||||||
221 | string $method, |
||||||
222 | ): Assertion { |
||||||
223 | $httpUtils = new Utils\HTTP(); |
||||||
0 ignored issues
–
show
|
|||||||
224 | $randomUtils = new Utils\Random(); |
||||||
225 | $timeUtils = new Utils\Time(); |
||||||
226 | |||||||
227 | $issueInstant = $timeUtils->generateTimestamp(); |
||||||
0 ignored issues
–
show
|
|||||||
228 | $notBefore = DateInterval::createFromDateString('30 seconds'); |
||||||
229 | $notOnOrAfter = DateInterval::createFromDateString(sprintf('%d seconds', $assertionLifetime)); |
||||||
230 | $assertionID = $randomUtils->generateID(); |
||||||
231 | $nameidFormat = 'http://schemas.xmlsoap.org/claims/UPN'; |
||||||
232 | $nameid = htmlspecialchars($nameid); |
||||||
233 | $now = new DateTimeImmutable('now', new DateTimeZone('Z')); |
||||||
234 | |||||||
235 | $audience = new Audience($target); |
||||||
236 | $audienceRestrictionCondition = new AudienceRestrictionCondition([$audience]); |
||||||
237 | $conditions = new Conditions( |
||||||
238 | [$audienceRestrictionCondition], |
||||||
239 | [], |
||||||
240 | [], |
||||||
241 | $now->sub($notBefore), |
||||||
242 | $now->add($notOnOrAfter), |
||||||
243 | ); |
||||||
244 | |||||||
245 | $nameIdentifier = new NameIdentifier($nameid, null, $nameidFormat); |
||||||
246 | $subject = new Subject(null, $nameIdentifier); |
||||||
247 | |||||||
248 | $authenticationStatement = new AuthenticationStatement($subject, $method, $now); |
||||||
249 | |||||||
250 | $attrs = []; |
||||||
251 | $attrUtils = new Utils\Attributes(); |
||||||
252 | foreach ($attributes as $name => $values) { |
||||||
253 | if ((!is_array($values)) || (count($values) == 0)) { |
||||||
254 | continue; |
||||||
255 | } |
||||||
256 | |||||||
257 | list($namespace, $name) = $attrUtils->getAttributeNamespace( |
||||||
258 | $name, |
||||||
259 | 'http://schemas.xmlsoap.org/claims', |
||||||
260 | ); |
||||||
261 | |||||||
262 | $namespace = htmlspecialchars($namespace); |
||||||
263 | $name = htmlspecialchars($name); |
||||||
264 | $attrValue = []; |
||||||
265 | foreach ($values as $value) { |
||||||
266 | if ((!isset($value)) || ($value === '')) { |
||||||
267 | continue; |
||||||
268 | } |
||||||
269 | $attrValue[] = new AttributeValue($value); |
||||||
270 | } |
||||||
271 | $attrs[] = new Attribute($name, $namespace, $attrValue); |
||||||
272 | } |
||||||
273 | $attributeStatement = new AttributeStatement($subject, $attrs); |
||||||
274 | |||||||
275 | return new Assertion( |
||||||
276 | $assertionID, |
||||||
277 | $issuer, |
||||||
278 | $now, |
||||||
279 | $conditions, |
||||||
280 | null, // Advice |
||||||
281 | [$authenticationStatement, $attributeStatement], |
||||||
282 | ); |
||||||
283 | } |
||||||
284 | |||||||
285 | |||||||
286 | /** |
||||||
287 | * @param string $issuer |
||||||
288 | * @param string $target |
||||||
289 | * @param string $nameid |
||||||
290 | * @param array $attributes |
||||||
291 | * @param int $assertionLifetime |
||||||
292 | * @return \SimpleSAML\SAML11\XML\saml\Assertion |
||||||
293 | */ |
||||||
294 | private static function generatePassiveAssertion( |
||||||
295 | string $issuer, |
||||||
296 | string $target, |
||||||
297 | string $nameid, |
||||||
298 | array $attributes, |
||||||
299 | int $assertionLifetime, |
||||||
300 | ): Assertion { |
||||||
301 | $httpUtils = new Utils\HTTP(); |
||||||
302 | $randomUtils = new Utils\Random(); |
||||||
303 | $timeUtils = new Utils\Time(); |
||||||
304 | |||||||
305 | $issueInstant = $timeUtils->generateTimestamp(); |
||||||
0 ignored issues
–
show
|
|||||||
306 | $notBefore = DateInterval::createFromDateString('30 seconds'); |
||||||
307 | $notOnOrAfter = DateInterval::createFromDateString(sprintf('%d seconds', $assertionLifetime)); |
||||||
308 | $assertionID = $randomUtils->generateID(); |
||||||
309 | $now = new DateTimeImmutable('now', new DateTimeZone('Z')); |
||||||
310 | |||||||
311 | if ($httpUtils->isHTTPS()) { |
||||||
312 | $method = SAML2_C::AC_PASSWORD_PROTECTED_TRANSPORT; |
||||||
313 | } else { |
||||||
314 | $method = C::AC_PASSWORD; |
||||||
315 | } |
||||||
316 | |||||||
317 | $audience = new Audience($target); |
||||||
318 | $audienceRestrictionCondition = new AudienceRestrictionCondition([$audience]); |
||||||
319 | $conditions = new Conditions( |
||||||
320 | [$audienceRestrictionCondition], |
||||||
321 | [], |
||||||
322 | [], |
||||||
323 | $now->sub($notBefore), |
||||||
324 | $now->add($notOnOrAfter), |
||||||
325 | ); |
||||||
326 | |||||||
327 | $nameIdentifier = new NameIdentifier($nameid, null, C::NAMEID_UNSPECIFIED); |
||||||
328 | $subject = new Subject(new SubjectConfirmation([new ConfirmationMethod(C::CM_BEARER)]), $nameIdentifier); |
||||||
329 | |||||||
330 | $authenticationStatement = new AuthenticationStatement($subject, $method, $now); |
||||||
331 | |||||||
332 | $attrs = []; |
||||||
333 | $attrs[] = new Attribute( |
||||||
334 | 'UPN', |
||||||
335 | 'http://schemas.xmlsoap.org/claims', |
||||||
336 | [new AttributeValue($attributes['http://schemas.xmlsoap.org/claims/UPN'][0])], |
||||||
337 | ); |
||||||
338 | $attrs[] = new Attribute( |
||||||
339 | 'ImmutableID', |
||||||
340 | 'http://schemas.microsoft.com/LiveID/Federation/2008/05', |
||||||
341 | [new AttributeValue($attributes['http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID'][0])], |
||||||
342 | ); |
||||||
343 | |||||||
344 | $attributeStatement = new AttributeStatement($subject, $attrs); |
||||||
345 | |||||||
346 | return new Assertion( |
||||||
347 | $assertionID, |
||||||
348 | $issuer, |
||||||
349 | $now, |
||||||
350 | $conditions, |
||||||
351 | null, // Advice |
||||||
352 | [$attributeStatement, $authenticationStatement], |
||||||
353 | ); |
||||||
354 | } |
||||||
355 | |||||||
356 | |||||||
357 | /** |
||||||
358 | * @param \SimpleSAML\SAML11\XML\saml\Assertion $assertion |
||||||
359 | * @param string $key |
||||||
360 | * @param string $cert |
||||||
361 | * @param string $algo |
||||||
362 | * @param string|null $passphrase |
||||||
363 | * @return \SimpleSAML\SAML11\XML\saml\Assertion |
||||||
364 | */ |
||||||
365 | private static function signAssertion( |
||||||
366 | Assertion $assertion, |
||||||
367 | string $key, |
||||||
368 | string $cert, |
||||||
369 | string $algo, |
||||||
370 | #[\SensitiveParameter] |
||||||
371 | ?string $passphrase = null, |
||||||
372 | ): Assertion { |
||||||
373 | $key = PrivateKey::fromFile($key, $passphrase); |
||||||
0 ignored issues
–
show
It seems like
$passphrase can also be of type null ; however, parameter $passphrase of SimpleSAML\XMLSecurity\Key\PrivateKey::fromFile() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
374 | $pubkey = PublicKey::fromFile($cert); |
||||||
375 | $keyInfo = new KeyInfo([ |
||||||
376 | new X509Data( |
||||||
377 | [new X509Certificate( |
||||||
378 | trim(chunk_split(base64_encode($pubkey->getPEM()->data()))), |
||||||
379 | )], |
||||||
380 | ), |
||||||
381 | ]); |
||||||
382 | |||||||
383 | $signer = (new SignatureAlgorithmFactory())->getAlgorithm( |
||||||
384 | $algo, |
||||||
385 | $key, |
||||||
386 | ); |
||||||
387 | |||||||
388 | $assertion->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo); |
||||||
389 | return $assertion; |
||||||
390 | } |
||||||
391 | |||||||
392 | |||||||
393 | /** |
||||||
394 | * @param string $wreply |
||||||
395 | * @param string $wresult |
||||||
396 | * @param ?string $wctx |
||||||
397 | */ |
||||||
398 | private static function postResponse(string $wreply, string $wresult, ?string $wctx): void |
||||||
399 | { |
||||||
400 | $config = Configuration::getInstance(); |
||||||
401 | $t = new Template($config, 'adfs:postResponse.twig'); |
||||||
402 | $t->data['wreply'] = $wreply; |
||||||
403 | $t->data['wresult'] = $wresult; |
||||||
404 | $t->data['wctx'] = $wctx; |
||||||
405 | $t->send(); |
||||||
406 | // Idp->postAuthProc expects this function to exit |
||||||
407 | exit(); |
||||||
0 ignored issues
–
show
|
|||||||
408 | } |
||||||
409 | |||||||
410 | |||||||
411 | /** |
||||||
412 | * @param array $state |
||||||
413 | * @throws \Exception |
||||||
414 | */ |
||||||
415 | public static function sendPassiveResponse(array $state): void |
||||||
416 | { |
||||||
417 | $idp = IdP::getByState($state); |
||||||
418 | $idpMetadata = $idp->getConfig(); |
||||||
419 | $idpEntityId = $idpMetadata->getString('entityid'); |
||||||
420 | |||||||
421 | $spMetadata = $state['SPMetadata']; |
||||||
422 | $spEntityId = $spMetadata['entityid']; |
||||||
423 | $spMetadata = Configuration::loadFromArray( |
||||||
424 | $spMetadata, |
||||||
425 | '$metadata[' . var_export($spEntityId, true) . ']', |
||||||
426 | ); |
||||||
427 | |||||||
428 | $assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null); |
||||||
429 | if ($assertionLifetime === null) { |
||||||
430 | $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300); |
||||||
431 | } |
||||||
432 | |||||||
433 | $now = new DateTimeImmutable('now', new DateTimeZone('Z')); |
||||||
434 | $created = $now->sub(DateInterval::createFromDateString(sprintf('30 seconds'))); |
||||||
435 | $expires = $now->add(DateInterval::createFromDateString(sprintf('%d seconds', $assertionLifetime))); |
||||||
436 | |||||||
437 | $attributes = $state['Attributes']; |
||||||
438 | $nameid = $state['saml:NameID'][SAML2_C::NAMEID_UNSPECIFIED]; |
||||||
439 | |||||||
440 | $assertion = ADFS::generatePassiveAssertion($idpEntityId, $spEntityId, $nameid->getValue(), $attributes, $assertionLifetime); |
||||||
0 ignored issues
–
show
It seems like
$assertionLifetime can also be of type null ; however, parameter $assertionLifetime of SimpleSAML\Module\adfs\I...eratePassiveAssertion() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
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); |
||||||
0 ignored issues
–
show
It seems like
$algo can also be of type null ; however, parameter $algo of SimpleSAML\Module\adfs\IdP\ADFS::signAssertion() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
457 | $assertion = Assertion::fromXML($assertion->toXML()); |
||||||
458 | } |
||||||
459 | |||||||
460 | $requestedSecurityToken = new RequestedSecurityToken($assertion); |
||||||
461 | $lifetime = new LifeTime(new Created($created), new Expires($expires)); |
||||||
462 | $appliesTo = new AppliesTo([new EndpointReference(new Address($spEntityId))]); |
||||||
463 | |||||||
464 | $requestedAttachedReference = new RequestedAttachedReference( |
||||||
465 | new SecurityTokenReference(null, null, [ |
||||||
466 | new KeyIdentifier( |
||||||
467 | $assertion->getId(), |
||||||
468 | 'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID', |
||||||
469 | ), |
||||||
470 | ]), |
||||||
471 | ); |
||||||
472 | $requestedUnattachedReference = new RequestedUnattachedReference( |
||||||
473 | new SecurityTokenReference(null, null, [ |
||||||
474 | new KeyIdentifier( |
||||||
475 | $assertion->getId(), |
||||||
476 | 'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID', |
||||||
477 | ), |
||||||
478 | ]), |
||||||
479 | ); |
||||||
480 | $tokenType = new TokenType(C::NS_SAML); |
||||||
481 | $requestType = new RequestType([RequestTypeEnum::Issue]); |
||||||
482 | $keyType = new KeyType(['http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey']); |
||||||
483 | |||||||
484 | $requestSecurityTokenResponse = new RequestSecurityTokenResponse(null, [ |
||||||
485 | $lifetime, |
||||||
486 | $appliesTo, |
||||||
487 | $requestedSecurityToken, |
||||||
488 | $requestedAttachedReference, |
||||||
489 | $requestedUnattachedReference, |
||||||
490 | $tokenType, |
||||||
491 | $requestType, |
||||||
492 | $keyType, |
||||||
493 | ]); |
||||||
494 | |||||||
495 | // Build envelope |
||||||
496 | $mustUnderstand = new XMLAttribute(SOAP_C::NS_SOAP_ENV_12, 'env', 'mustUnderstand', '1'); |
||||||
497 | $header = new Header([ |
||||||
498 | new Action('http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue', [$mustUnderstand]), |
||||||
499 | new RelatesTo($state['MessageID'], null), |
||||||
500 | new Security( |
||||||
501 | [ |
||||||
502 | new Timestamp( |
||||||
503 | new Created($created), |
||||||
504 | new Expires($expires), |
||||||
505 | ), |
||||||
506 | ], |
||||||
507 | [$mustUnderstand], |
||||||
508 | ), |
||||||
509 | ]); |
||||||
510 | $body = new Body(null, [$requestSecurityTokenResponse]); |
||||||
511 | $envelope = new Envelope($body, $header); |
||||||
512 | |||||||
513 | $xmlResponse = $envelope->toXML(); |
||||||
514 | \SimpleSAML\Logger::debug($xmlResponse->ownerDocument->saveXML($xmlResponse)); |
||||||
0 ignored issues
–
show
The method
saveXML() does not exist on null .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
515 | |||||||
516 | echo $xmlResponse->ownerDocument->saveXML($xmlResponse); |
||||||
517 | exit(); |
||||||
0 ignored issues
–
show
|
|||||||
518 | } |
||||||
519 | |||||||
520 | |||||||
521 | /** |
||||||
522 | * @param array $state |
||||||
523 | * @throws \Exception |
||||||
524 | */ |
||||||
525 | public static function sendResponse(array $state): void |
||||||
526 | { |
||||||
527 | $spMetadata = $state['SPMetadata']; |
||||||
528 | $spEntityId = $spMetadata['entityid']; |
||||||
529 | $spMetadata = Configuration::loadFromArray( |
||||||
530 | $spMetadata, |
||||||
531 | '$metadata[' . var_export($spEntityId, true) . ']', |
||||||
532 | ); |
||||||
533 | |||||||
534 | $attributes = $state['Attributes']; |
||||||
535 | |||||||
536 | $nameidattribute = $spMetadata->getValue('simplesaml.nameidattribute'); |
||||||
537 | if (!empty($nameidattribute)) { |
||||||
538 | if (!array_key_exists($nameidattribute, $attributes)) { |
||||||
539 | throw new Exception('simplesaml.nameidattribute does not exist in resulting attribute set'); |
||||||
540 | } |
||||||
541 | $nameid = $attributes[$nameidattribute][0]; |
||||||
542 | } else { |
||||||
543 | $randomUtils = new Utils\Random(); |
||||||
544 | $nameid = $randomUtils->generateID(); |
||||||
545 | } |
||||||
546 | |||||||
547 | $idp = IdP::getByState($state); |
||||||
548 | $idpMetadata = $idp->getConfig(); |
||||||
549 | $idpEntityId = $idpMetadata->getString('entityid'); |
||||||
550 | |||||||
551 | $idp->addAssociation([ |
||||||
552 | 'id' => 'adfs:' . $spEntityId, |
||||||
553 | 'Handler' => ADFS::class, |
||||||
554 | 'adfs:entityID' => $spEntityId, |
||||||
555 | ]); |
||||||
556 | |||||||
557 | $assertionLifetime = $spMetadata->getOptionalInteger('assertion.lifetime', null); |
||||||
558 | if ($assertionLifetime === null) { |
||||||
559 | $assertionLifetime = $idpMetadata->getOptionalInteger('assertion.lifetime', 300); |
||||||
560 | } |
||||||
561 | |||||||
562 | if (isset($state['saml:AuthnContextClassRef'])) { |
||||||
563 | $method = $state['saml:AuthnContextClassRef']; |
||||||
564 | } elseif ((new Utils\HTTP())->isHTTPS()) { |
||||||
565 | $method = SAML2_C::AC_PASSWORD_PROTECTED_TRANSPORT; |
||||||
566 | } else { |
||||||
567 | $method = C::AC_PASSWORD; |
||||||
568 | } |
||||||
569 | |||||||
570 | $assertion = ADFS::generateActiveAssertion($idpEntityId, $spEntityId, $nameid, $attributes, $assertionLifetime, $method); |
||||||
0 ignored issues
–
show
It seems like
$assertionLifetime can also be of type null ; however, parameter $assertionLifetime of SimpleSAML\Module\adfs\I...nerateActiveAssertion() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
571 | |||||||
572 | $privateKeyCfg = $idpMetadata->getOptionalString('privatekey', null); |
||||||
573 | $certificateCfg = $idpMetadata->getOptionalString('certificate', null); |
||||||
574 | |||||||
575 | if ($privateKeyCfg !== null && $certificateCfg !== null) { |
||||||
576 | $configUtils = new Utils\Config(); |
||||||
577 | $privateKeyFile = $configUtils->getCertPath($privateKeyCfg); |
||||||
578 | $certificateFile = $configUtils->getCertPath($certificateCfg); |
||||||
579 | $passphrase = $idpMetadata->getOptionalString('privatekey_pass', null); |
||||||
580 | |||||||
581 | $algo = $spMetadata->getOptionalString('signature.algorithm', null); |
||||||
582 | if ($algo === null) { |
||||||
583 | $algo = $idpMetadata->getOptionalString('signature.algorithm', C::SIG_RSA_SHA256); |
||||||
584 | } |
||||||
585 | |||||||
586 | $assertion = ADFS::signAssertion($assertion, $privateKeyFile, $certificateFile, $algo, $passphrase); |
||||||
0 ignored issues
–
show
It seems like
$algo can also be of type null ; however, parameter $algo of SimpleSAML\Module\adfs\IdP\ADFS::signAssertion() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
587 | $assertion = Assertion::fromXML($assertion->toXML()); |
||||||
588 | } |
||||||
589 | |||||||
590 | $requestSecurityToken = new RequestSecurityToken(null, [$assertion]); |
||||||
591 | $appliesTo = new AppliesTo([new EndpointReference(new Address($spEntityId))]); |
||||||
592 | $requestSecurityTokenResponse = new RequestSecurityTokenResponse(null, [$requestSecurityToken, $appliesTo]); |
||||||
593 | |||||||
594 | $xmlResponse = $requestSecurityTokenResponse->toXML(); |
||||||
595 | $wresult = $xmlResponse->ownerDocument->saveXML($xmlResponse); |
||||||
596 | Logger::debug($wresult); |
||||||
597 | |||||||
598 | $wctx = $state['adfs:wctx']; |
||||||
599 | $wreply = $state['adfs:wreply'] ? : $spMetadata->getValue('prp'); |
||||||
600 | ADFS::postResponse($wreply, $wresult, $wctx); |
||||||
601 | } |
||||||
602 | |||||||
603 | |||||||
604 | /** |
||||||
605 | * @param \SimpleSAML\IdP $idp |
||||||
606 | * @param array $state |
||||||
607 | */ |
||||||
608 | public static function sendLogoutResponse(IdP $idp, array $state): void |
||||||
0 ignored issues
–
show
The parameter
$state is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
609 | { |
||||||
610 | // NB:: we don't know from which SP the logout request came from |
||||||
611 | $idpMetadata = $idp->getConfig(); |
||||||
612 | $httpUtils = new Utils\HTTP(); |
||||||
613 | $httpUtils->redirectTrustedURL( |
||||||
614 | $idpMetadata->getOptionalString('redirect-after-logout', $httpUtils->getBaseURL()), |
||||||
0 ignored issues
–
show
It seems like
$idpMetadata->getOptiona...ttpUtils->getBaseURL()) can also be of type null ; however, parameter $url of SimpleSAML\Utils\HTTP::redirectTrustedURL() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
615 | ); |
||||||
616 | } |
||||||
617 | |||||||
618 | |||||||
619 | /** |
||||||
620 | * @param \SimpleSAML\IdP $idp |
||||||
621 | * @throws \Exception |
||||||
622 | */ |
||||||
623 | public static function receiveLogoutMessage(IdP $idp): void |
||||||
624 | { |
||||||
625 | // if a redirect is to occur based on wreply, we will redirect to url as |
||||||
626 | // this implies an override to normal sp notification |
||||||
627 | if (isset($_GET['wreply']) && !empty($_GET['wreply'])) { |
||||||
628 | $httpUtils = new Utils\HTTP(); |
||||||
629 | $idp->doLogoutRedirect($httpUtils->checkURLAllowed($_GET['wreply'])); |
||||||
630 | throw new Exception("Code should never be reached"); |
||||||
631 | } |
||||||
632 | |||||||
633 | $state = [ |
||||||
634 | 'Responder' => [ADFS::class, 'sendLogoutResponse'], |
||||||
635 | ]; |
||||||
636 | $assocId = null; |
||||||
637 | // TODO: verify that this is really no problem for: |
||||||
638 | // a) SSP, because there's no caller SP. |
||||||
639 | // b) ADFS SP because caller will be called back.. |
||||||
640 | $idp->handleLogoutRequest($state, $assocId); |
||||||
641 | } |
||||||
642 | |||||||
643 | |||||||
644 | /** |
||||||
645 | * accepts an association array, and returns a URL that can be accessed to terminate the association |
||||||
646 | * |
||||||
647 | * @param \SimpleSAML\IdP $idp |
||||||
648 | * @param array $association |
||||||
649 | * @param string $relayState |
||||||
650 | * @return string |
||||||
651 | */ |
||||||
652 | public static function getLogoutURL(IdP $idp, array $association, string $relayState): string |
||||||
653 | { |
||||||
654 | $metadata = MetaDataStorageHandler::getMetadataHandler(); |
||||||
655 | $spMetadata = $metadata->getMetaDataConfig($association['adfs:entityID'], 'adfs-sp-remote'); |
||||||
656 | $returnTo = Module::getModuleURL( |
||||||
657 | 'adfs/idp/prp.php?assocId=' . urlencode($association["id"]) . '&relayState=' . urlencode($relayState), |
||||||
658 | ); |
||||||
659 | return $spMetadata->getValue('prp') . '?wa=wsignoutcleanup1.0&wreply=' . urlencode($returnTo); |
||||||
660 | } |
||||||
661 | } |
||||||
662 |