ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
SAML2.php
Go to the documentation of this file.
1<?php
2
3
5
12{
13
19 public static function sendResponse(array $state)
20 {
21 assert('isset($state["Attributes"])');
22 assert('isset($state["SPMetadata"])');
23 assert('isset($state["saml:ConsumerURL"])');
24 assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL
25 assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
26
27 $spMetadata = $state["SPMetadata"];
28 $spEntityId = $spMetadata['entityid'];
31 '$metadata['.var_export($spEntityId, true).']'
32 );
33
34 SimpleSAML\Logger::info('Sending SAML 2.0 Response to '.var_export($spEntityId, true));
35
36 $requestId = $state['saml:RequestId'];
37 $relayState = $state['saml:RelayState'];
38 $consumerURL = $state['saml:ConsumerURL'];
39 $protocolBinding = $state['saml:Binding'];
40
42
43 $idpMetadata = $idp->getConfig();
44
46
47 if (isset($state['saml:AuthenticatingAuthority'])) {
48 $assertion->setAuthenticatingAuthority($state['saml:AuthenticatingAuthority']);
49 }
50
51 // create the session association (for logout)
52 $association = array(
53 'id' => 'saml:'.$spEntityId,
54 'Handler' => 'sspmod_saml_IdP_SAML2',
55 'Expires' => $assertion->getSessionNotOnOrAfter(),
56 'saml:entityID' => $spEntityId,
57 'saml:NameID' => $state['saml:idp:NameID'],
58 'saml:SessionIndex' => $assertion->getSessionIndex(),
59 );
60
61 // maybe encrypt the assertion
62 $assertion = self::encryptAssertion($idpMetadata, $spMetadata, $assertion);
63
64 // create the response
65 $ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
66 $ar->setInResponseTo($requestId);
67 $ar->setRelayState($relayState);
68 $ar->setAssertions(array($assertion));
69
70 // register the session association with the IdP
71 $idp->addAssociation($association);
72
73 $statsData = array(
74 'spEntityID' => $spEntityId,
75 'idpEntityID' => $idpMetadata->getString('entityid'),
76 'protocol' => 'saml2',
77 );
78 if (isset($state['saml:AuthnRequestReceivedAt'])) {
79 $statsData['logintime'] = microtime(true) - $state['saml:AuthnRequestReceivedAt'];
80 }
81 SimpleSAML_Stats::log('saml:idp:Response', $statsData);
82
83 // send the response
84 $binding = \SAML2\Binding::getBinding($protocolBinding);
85 $binding->send($ar);
86 }
87
88
96 public static function handleAuthError(SimpleSAML_Error_Exception $exception, array $state)
97 {
98 assert('isset($state["SPMetadata"])');
99 assert('isset($state["saml:ConsumerURL"])');
100 assert('array_key_exists("saml:RequestId", $state)'); // Can be NULL.
101 assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
102
103 $spMetadata = $state["SPMetadata"];
104 $spEntityId = $spMetadata['entityid'];
107 '$metadata['.var_export($spEntityId, true).']'
108 );
109
110 $requestId = $state['saml:RequestId'];
111 $relayState = $state['saml:RelayState'];
112 $consumerURL = $state['saml:ConsumerURL'];
113 $protocolBinding = $state['saml:Binding'];
114
116
117 $idpMetadata = $idp->getConfig();
118
120
121 SimpleSAML\Logger::warning("Returning error to SP with entity ID '".var_export($spEntityId, true)."'.");
122 $exception->log(SimpleSAML\Logger::WARNING);
123
124 $ar = self::buildResponse($idpMetadata, $spMetadata, $consumerURL);
125 $ar->setInResponseTo($requestId);
126 $ar->setRelayState($relayState);
127
128 $status = array(
129 'Code' => $error->getStatus(),
130 'SubCode' => $error->getSubStatus(),
131 'Message' => $error->getStatusMessage(),
132 );
133 $ar->setStatus($status);
134
135 $statsData = array(
136 'spEntityID' => $spEntityId,
137 'idpEntityID' => $idpMetadata->getString('entityid'),
138 'protocol' => 'saml2',
139 'error' => $status,
140 );
141 if (isset($state['saml:AuthnRequestReceivedAt'])) {
142 $statsData['logintime'] = microtime(true) - $state['saml:AuthnRequestReceivedAt'];
143 }
144 SimpleSAML_Stats::log('saml:idp:Response:error', $statsData);
145
146 $binding = \SAML2\Binding::getBinding($protocolBinding);
147 $binding->send($ar);
148 }
149
150
162 private static function getAssertionConsumerService(
163 array $supportedBindings,
165 $AssertionConsumerServiceURL,
166 $ProtocolBinding,
167 $AssertionConsumerServiceIndex
168 ) {
169 assert('is_string($AssertionConsumerServiceURL) || is_null($AssertionConsumerServiceURL)');
170 assert('is_string($ProtocolBinding) || is_null($ProtocolBinding)');
171 assert('is_int($AssertionConsumerServiceIndex) || is_null($AssertionConsumerServiceIndex)');
172
173 /* We want to pick the best matching endpoint in the case where for example
174 * only the ProtocolBinding is given. We therefore pick endpoints with the
175 * following priority:
176 * 1. isDefault="true"
177 * 2. isDefault unset
178 * 3. isDefault="false"
179 */
180 $firstNotFalse = null;
181 $firstFalse = null;
182 foreach ($spMetadata->getEndpoints('AssertionConsumerService') as $ep) {
183 if ($AssertionConsumerServiceURL !== null && $ep['Location'] !== $AssertionConsumerServiceURL) {
184 continue;
185 }
186 if ($ProtocolBinding !== null && $ep['Binding'] !== $ProtocolBinding) {
187 continue;
188 }
189 if ($AssertionConsumerServiceIndex !== null && $ep['index'] !== $AssertionConsumerServiceIndex) {
190 continue;
191 }
192
193 if (!in_array($ep['Binding'], $supportedBindings, true)) {
194 /* The endpoint has an unsupported binding. */
195 continue;
196 }
197
198 // we have an endpoint that matches all our requirements. Check if it is the best one
199
200 if (array_key_exists('isDefault', $ep)) {
201 if ($ep['isDefault'] === true) {
202 // this is the first matching endpoint with isDefault set to true
203 return $ep;
204 }
205 // isDefault is set to FALSE, but the endpoint is still usable
206 if ($firstFalse === null) {
207 // this is the first endpoint that we can use
208 $firstFalse = $ep;
209 }
210 } else {
211 if ($firstNotFalse === null) {
212 // this is the first endpoint without isDefault set
213 $firstNotFalse = $ep;
214 }
215 }
216 }
217
218 if ($firstNotFalse !== null) {
219 return $firstNotFalse;
220 } elseif ($firstFalse !== null) {
221 return $firstFalse;
222 }
223
224 SimpleSAML\Logger::warning('Authentication request specifies invalid AssertionConsumerService:');
225 if ($AssertionConsumerServiceURL !== null) {
226 SimpleSAML\Logger::warning('AssertionConsumerServiceURL: '.var_export($AssertionConsumerServiceURL, true));
227 }
228 if ($ProtocolBinding !== null) {
229 SimpleSAML\Logger::warning('ProtocolBinding: '.var_export($ProtocolBinding, true));
230 }
231 if ($AssertionConsumerServiceIndex !== null) {
233 'AssertionConsumerServiceIndex: '.var_export($AssertionConsumerServiceIndex, true)
234 );
235 }
236
237 // we have no good endpoints. Our last resort is to just use the default endpoint
238 return $spMetadata->getDefaultEndpoint('AssertionConsumerService', $supportedBindings);
239 }
240
241
249 {
250
252 $idpMetadata = $idp->getConfig();
253
254 $supportedBindings = array(\SAML2\Constants::BINDING_HTTP_POST);
255 if ($idpMetadata->getBoolean('saml20.sendartifact', false)) {
256 $supportedBindings[] = \SAML2\Constants::BINDING_HTTP_ARTIFACT;
257 }
258 if ($idpMetadata->getBoolean('saml20.hok.assertion', false)) {
259 $supportedBindings[] = \SAML2\Constants::BINDING_HOK_SSO;
260 }
261
262 if (isset($_REQUEST['spentityid'])) {
263 /* IdP initiated authentication. */
264
265 if (isset($_REQUEST['cookieTime'])) {
266 $cookieTime = (int) $_REQUEST['cookieTime'];
267 if ($cookieTime + 5 > time()) {
268 /*
269 * Less than five seconds has passed since we were
270 * here the last time. Cookies are probably disabled.
271 */
273 }
274 }
275
276 $spEntityId = (string) $_REQUEST['spentityid'];
277 $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
278
279 if (isset($_REQUEST['RelayState'])) {
280 $relayState = (string) $_REQUEST['RelayState'];
281 } else {
282 $relayState = null;
283 }
284
285 if (isset($_REQUEST['binding'])) {
286 $protocolBinding = (string) $_REQUEST['binding'];
287 } else {
288 $protocolBinding = null;
289 }
290
291 if (isset($_REQUEST['NameIDFormat'])) {
292 $nameIDFormat = (string) $_REQUEST['NameIDFormat'];
293 } else {
294 $nameIDFormat = null;
295 }
296
297 $requestId = null;
298 $IDPList = array();
299 $ProxyCount = null;
300 $RequesterID = null;
301 $forceAuthn = false;
302 $isPassive = false;
303 $consumerURL = null;
304 $consumerIndex = null;
305 $extensions = null;
306 $allowCreate = true;
307 $authnContext = null;
308
309 $idpInit = true;
310
312 'SAML2.0 - IdP.SSOService: IdP initiated authentication: '.var_export($spEntityId, true)
313 );
314 } else {
316 $request = $binding->receive();
317
318 if (!($request instanceof \SAML2\AuthnRequest)) {
320 'Message received on authentication request endpoint wasn\'t an authentication request.'
321 );
322 }
323
324 $spEntityId = $request->getIssuer();
325 if ($spEntityId === null) {
327 'Received message on authentication request endpoint without issuer.'
328 );
329 }
330 $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
331
333
334 $relayState = $request->getRelayState();
335
336 $requestId = $request->getId();
337 $IDPList = $request->getIDPList();
338 $ProxyCount = $request->getProxyCount();
339 if ($ProxyCount !== null) {
340 $ProxyCount--;
341 }
342 $RequesterID = $request->getRequesterID();
343 $forceAuthn = $request->getForceAuthn();
344 $isPassive = $request->getIsPassive();
345 $consumerURL = $request->getAssertionConsumerServiceURL();
346 $protocolBinding = $request->getProtocolBinding();
347 $consumerIndex = $request->getAssertionConsumerServiceIndex();
348 $extensions = $request->getExtensions();
349 $authnContext = $request->getRequestedAuthnContext();
350
351 $nameIdPolicy = $request->getNameIdPolicy();
352 if (isset($nameIdPolicy['Format'])) {
353 $nameIDFormat = $nameIdPolicy['Format'];
354 } else {
355 $nameIDFormat = null;
356 }
357 if (isset($nameIdPolicy['AllowCreate'])) {
358 $allowCreate = $nameIdPolicy['AllowCreate'];
359 } else {
360 $allowCreate = false;
361 }
362
363 $idpInit = false;
364
366 'SAML2.0 - IdP.SSOService: incoming authentication request: '.var_export($spEntityId, true)
367 );
368 }
369
370 SimpleSAML_Stats::log('saml:idp:AuthnRequest', array(
371 'spEntityID' => $spEntityId,
372 'idpEntityID' => $idpMetadata->getString('entityid'),
373 'forceAuthn' => $forceAuthn,
374 'isPassive' => $isPassive,
375 'protocol' => 'saml2',
376 'idpInit' => $idpInit,
377 ));
378
380 $supportedBindings,
382 $consumerURL,
383 $protocolBinding,
384 $consumerIndex
385 );
386
387 $IDPList = array_unique(array_merge($IDPList, $spMetadata->getArrayizeString('IDPList', array())));
388 if ($ProxyCount === null) {
389 $ProxyCount = $spMetadata->getInteger('ProxyCount', null);
390 }
391
392 if (!$forceAuthn) {
393 $forceAuthn = $spMetadata->getBoolean('ForceAuthn', false);
394 }
395
396 $sessionLostParams = array(
397 'spentityid' => $spEntityId,
398 'cookieTime' => time(),
399 );
400 if ($relayState !== null) {
401 $sessionLostParams['RelayState'] = $relayState;
402 }
403
404 $sessionLostURL = \SimpleSAML\Utils\HTTP::addURLParameters(
405 \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(),
406 $sessionLostParams
407 );
408
409 $state = array(
410 'Responder' => array('sspmod_saml_IdP_SAML2', 'sendResponse'),
411 SimpleSAML_Auth_State::EXCEPTION_HANDLER_FUNC => array('sspmod_saml_IdP_SAML2', 'handleAuthError'),
412 SimpleSAML_Auth_State::RESTART => $sessionLostURL,
413
414 'SPMetadata' => $spMetadata->toArray(),
415 'saml:RelayState' => $relayState,
416 'saml:RequestId' => $requestId,
417 'saml:IDPList' => $IDPList,
418 'saml:ProxyCount' => $ProxyCount,
419 'saml:RequesterID' => $RequesterID,
420 'ForceAuthn' => $forceAuthn,
421 'isPassive' => $isPassive,
422 'saml:ConsumerURL' => $acsEndpoint['Location'],
423 'saml:Binding' => $acsEndpoint['Binding'],
424 'saml:NameIDFormat' => $nameIDFormat,
425 'saml:AllowCreate' => $allowCreate,
426 'saml:Extensions' => $extensions,
427 'saml:AuthnRequestReceivedAt' => microtime(true),
428 'saml:RequestedAuthnContext' => $authnContext,
429 );
430
431 $idp->handleAuthenticationRequest($state);
432 }
433
434
443 {
444 assert('is_string($relayState) || is_null($relayState)');
445
446 SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '.var_export($association['saml:entityID'], true));
447
449 $idpMetadata = $idp->getConfig();
450 $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
451
452 SimpleSAML_Stats::log('saml:idp:LogoutRequest:sent', array(
453 'spEntityID' => $association['saml:entityID'],
454 'idpEntityID' => $idpMetadata->getString('entityid'),
455 ));
456
457 $dst = $spMetadata->getEndpointPrioritizedByBinding(
458 'SingleLogoutService',
459 array(
460 \SAML2\Constants::BINDING_HTTP_REDIRECT,
461 \SAML2\Constants::BINDING_HTTP_POST
462 )
463 );
466 $lr->setDestination($dst['Location']);
467
468 $binding->send($lr);
469 }
470
471
478 public static function sendLogoutResponse(SimpleSAML_IdP $idp, array $state)
479 {
480 assert('isset($state["saml:SPEntityId"])');
481 assert('isset($state["saml:RequestId"])');
482 assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL.
483
484 $spEntityId = $state['saml:SPEntityId'];
485
487 $idpMetadata = $idp->getConfig();
488 $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
489
491 $lr->setInResponseTo($state['saml:RequestId']);
492 $lr->setRelayState($state['saml:RelayState']);
493
494 if (isset($state['core:Failed']) && $state['core:Failed']) {
495 $partial = true;
496 $lr->setStatus(array(
497 'Code' => \SAML2\Constants::STATUS_SUCCESS,
498 'SubCode' => \SAML2\Constants::STATUS_PARTIAL_LOGOUT,
499 ));
500 SimpleSAML\Logger::info('Sending logout response for partial logout to SP '.var_export($spEntityId, true));
501 } else {
502 $partial = false;
503 SimpleSAML\Logger::debug('Sending logout response to SP '.var_export($spEntityId, true));
504 }
505
506 SimpleSAML_Stats::log('saml:idp:LogoutResponse:sent', array(
507 'spEntityID' => $spEntityId,
508 'idpEntityID' => $idpMetadata->getString('entityid'),
509 'partial' => $partial
510 ));
511 $dst = $spMetadata->getEndpointPrioritizedByBinding(
512 'SingleLogoutService',
513 array(
514 \SAML2\Constants::BINDING_HTTP_REDIRECT,
515 \SAML2\Constants::BINDING_HTTP_POST
516 )
517 );
519 if (isset($dst['ResponseLocation'])) {
520 $dst = $dst['ResponseLocation'];
521 } else {
522 $dst = $dst['Location'];
523 }
524 $lr->setDestination($dst);
525
526 $binding->send($lr);
527 }
528
529
537 {
538
540 $message = $binding->receive();
541
542 $spEntityId = $message->getIssuer();
543 if ($spEntityId === null) {
544 /* Without an issuer we have no way to respond to the message. */
545 throw new SimpleSAML_Error_BadRequest('Received message on logout endpoint without issuer.');
546 }
547
549 $idpMetadata = $idp->getConfig();
550 $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote');
551
553
554 if ($message instanceof \SAML2\LogoutResponse) {
555 SimpleSAML\Logger::info('Received SAML 2.0 LogoutResponse from: '.var_export($spEntityId, true));
556 $statsData = array(
557 'spEntityID' => $spEntityId,
558 'idpEntityID' => $idpMetadata->getString('entityid'),
559 );
560 if (!$message->isSuccess()) {
561 $statsData['error'] = $message->getStatus();
562 }
563 SimpleSAML_Stats::log('saml:idp:LogoutResponse:recv', $statsData);
564
565 $relayState = $message->getRelayState();
566
567 if (!$message->isSuccess()) {
569 SimpleSAML\Logger::warning('Unsuccessful logout. Status was: '.$logoutError);
570 } else {
571 $logoutError = null;
572 }
573
574 $assocId = 'saml:'.$spEntityId;
575
576 $idp->handleLogoutResponse($assocId, $relayState, $logoutError);
577 } elseif ($message instanceof \SAML2\LogoutRequest) {
578 SimpleSAML\Logger::info('Received SAML 2.0 LogoutRequest from: '.var_export($spEntityId, true));
579 SimpleSAML_Stats::log('saml:idp:LogoutRequest:recv', array(
580 'spEntityID' => $spEntityId,
581 'idpEntityID' => $idpMetadata->getString('entityid'),
582 ));
583
584 $spStatsId = $spMetadata->getString('core:statistics-id', $spEntityId);
585 SimpleSAML\Logger::stats('saml20-idp-SLO spinit '.$spStatsId.' '.$idpMetadata->getString('entityid'));
586
587 $state = array(
588 'Responder' => array('sspmod_saml_IdP_SAML2', 'sendLogoutResponse'),
589 'saml:SPEntityId' => $spEntityId,
590 'saml:RelayState' => $message->getRelayState(),
591 'saml:RequestId' => $message->getId(),
592 );
593
594 $assocId = 'saml:'.$spEntityId;
595 $idp->handleLogoutRequest($state, $assocId);
596 } else {
597 throw new SimpleSAML_Error_BadRequest('Unknown message received on logout endpoint: '.get_class($message));
598 }
599 }
600
601
612 {
613 assert('is_string($relayState) || is_null($relayState)');
614
615 SimpleSAML\Logger::info('Sending SAML 2.0 LogoutRequest to: '.var_export($association['saml:entityID'], true));
616
618 $idpMetadata = $idp->getConfig();
619 $spMetadata = $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
620
621 $bindings = array(
622 \SAML2\Constants::BINDING_HTTP_REDIRECT,
623 \SAML2\Constants::BINDING_HTTP_POST
624 );
625 $dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', $bindings);
626
627 if ($dst['Binding'] === \SAML2\Constants::BINDING_HTTP_POST) {
628 $params = array('association' => $association['id'], 'idp' => $idp->getId());
629 if ($relayState !== null) {
630 $params['RelayState'] = $relayState;
631 }
632 return SimpleSAML\Module::getModuleURL('core/idp/logout-iframe-post.php', $params);
633 }
634
636 $lr->setDestination($dst['Location']);
637
638 $binding = new \SAML2\HTTPRedirect();
639 return $binding->getRedirectURL($lr);
640 }
641
642
652 {
654 try {
655 return $metadata->getMetaDataConfig($association['saml:entityID'], 'saml20-sp-remote');
656 } catch (Exception $e) {
657 return SimpleSAML_Configuration::loadFromArray(array(), 'Unknown SAML 2 entity.');
658 }
659 }
660
661
671 private static function generateNameIdValue(
674 array &$state
675 ) {
676
677 $attribute = $spMetadata->getString('simplesaml.nameidattribute', null);
678 if ($attribute === null) {
679 $attribute = $idpMetadata->getString('simplesaml.nameidattribute', null);
680 if ($attribute === null) {
681 if (!isset($state['UserID'])) {
682 SimpleSAML\Logger::error('Unable to generate NameID. Check the userid.attribute option.');
683 return null;
684 }
685 $attributeValue = $state['UserID'];
686 $idpEntityId = $idpMetadata->getString('entityid');
687 $spEntityId = $spMetadata->getString('entityid');
688
690
691 $uidData = 'uidhashbase'.$secretSalt;
692 $uidData .= strlen($idpEntityId).':'.$idpEntityId;
693 $uidData .= strlen($spEntityId).':'.$spEntityId;
694 $uidData .= strlen($attributeValue).':'.$attributeValue;
695 $uidData .= $secretSalt;
696
697 return hash('sha1', $uidData);
698 }
699 }
700
701 $attributes = $state['Attributes'];
702 if (!array_key_exists($attribute, $attributes)) {
703 SimpleSAML\Logger::error('Unable to add NameID: Missing '.var_export($attribute, true).
704 ' in the attributes of the user.');
705 return null;
706 }
707
708 return $attributes[$attribute][0];
709 }
710
711
723 private static function encodeAttributes(
726 array $attributes
727 ) {
728
729 $base64Attributes = $spMetadata->getBoolean('base64attributes', null);
730 if ($base64Attributes === null) {
731 $base64Attributes = $idpMetadata->getBoolean('base64attributes', false);
732 }
733
734 if ($base64Attributes) {
735 $defaultEncoding = 'base64';
736 } else {
737 $defaultEncoding = 'string';
738 }
739
740 $srcEncodings = $idpMetadata->getArray('attributeencodings', array());
741 $dstEncodings = $spMetadata->getArray('attributeencodings', array());
742
743 /*
744 * Merge the two encoding arrays. Encodings specified in the target metadata
745 * takes precedence over the source metadata.
746 */
747 $encodings = array_merge($srcEncodings, $dstEncodings);
748
749 $ret = array();
750 foreach ($attributes as $name => $values) {
751 $ret[$name] = array();
752 if (array_key_exists($name, $encodings)) {
753 $encoding = $encodings[$name];
754 } else {
755 $encoding = $defaultEncoding;
756 }
757
758 foreach ($values as $value) {
759 // allow null values
760 if ($value === null) {
761 $ret[$name][] = $value;
762 continue;
763 }
764
765 $attrval = $value;
766 if ($value instanceof DOMNodeList) {
767 $attrval = new \SAML2\XML\saml\AttributeValue($value->item(0)->parentNode);
768 }
769
770 switch ($encoding) {
771 case 'string':
772 $value = (string) $attrval;
773 break;
774 case 'base64':
775 $value = base64_encode((string) $attrval);
776 break;
777 case 'raw':
778 if (is_string($value)) {
779 $doc = \SAML2\DOMDocumentFactory::fromString('<root>'.$value.'</root>');
780 $value = $doc->firstChild->childNodes;
781 }
782 assert('$value instanceof DOMNodeList || $value instanceof \SAML2\XML\saml\NameID');
783 break;
784 default:
785 throw new SimpleSAML_Error_Exception('Invalid encoding for attribute '.
786 var_export($name, true).': '.var_export($encoding, true));
787 }
788 $ret[$name][] = $value;
789 }
790 }
791
792 return $ret;
793 }
794
795
804 private static function getAttributeNameFormat(
807 ) {
808
809 // try SP metadata first
810 $attributeNameFormat = $spMetadata->getString('attributes.NameFormat', null);
811 if ($attributeNameFormat !== null) {
813 }
814 $attributeNameFormat = $spMetadata->getString('AttributeNameFormat', null);
815 if ($attributeNameFormat !== null) {
817 }
818
819 // look in IdP metadata
820 $attributeNameFormat = $idpMetadata->getString('attributes.NameFormat', null);
821 if ($attributeNameFormat !== null) {
823 }
824 $attributeNameFormat = $idpMetadata->getString('AttributeNameFormat', null);
825 if ($attributeNameFormat !== null) {
827 }
828
829 // default
830 return 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
831 }
832
833
845 private static function buildAssertion(
848 array &$state
849 ) {
850 assert('isset($state["Attributes"])');
851 assert('isset($state["saml:ConsumerURL"])');
852
853 $now = time();
854
855 $signAssertion = $spMetadata->getBoolean('saml20.sign.assertion', null);
856 if ($signAssertion === null) {
857 $signAssertion = $idpMetadata->getBoolean('saml20.sign.assertion', true);
858 }
859
861
862 $a = new \SAML2\Assertion();
863 if ($signAssertion) {
865 }
866
867 $a->setIssuer($idpMetadata->getString('entityid'));
868 $a->setValidAudiences(array($spMetadata->getString('entityid')));
869
870 $a->setNotBefore($now - 30);
871
872 $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
873 if ($assertionLifetime === null) {
874 $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
875 }
876 $a->setNotOnOrAfter($now + $assertionLifetime);
877
878 if (isset($state['saml:AuthnContextClassRef'])) {
879 $a->setAuthnContext($state['saml:AuthnContextClassRef']);
880 } else {
881 $a->setAuthnContext(\SAML2\Constants::AC_PASSWORD);
882 }
883
884 $sessionStart = $now;
885 if (isset($state['AuthnInstant'])) {
886 $a->setAuthnInstant($state['AuthnInstant']);
887 $sessionStart = $state['AuthnInstant'];
888 }
889
890 $sessionLifetime = $config->getInteger('session.duration', 8 * 60 * 60);
891 $a->setSessionNotOnOrAfter($sessionStart + $sessionLifetime);
892
893 $a->setSessionIndex(SimpleSAML\Utils\Random::generateID());
894
895 $sc = new \SAML2\XML\saml\SubjectConfirmation();
896 $sc->SubjectConfirmationData = new \SAML2\XML\saml\SubjectConfirmationData();
897 $sc->SubjectConfirmationData->NotOnOrAfter = $now + $assertionLifetime;
898 $sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL'];
899 $sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId'];
900
901 // ProtcolBinding of SP's <AuthnRequest> overwrites IdP hosted metadata configuration
902 $hokAssertion = null;
903 if ($state['saml:Binding'] === \SAML2\Constants::BINDING_HOK_SSO) {
904 $hokAssertion = true;
905 }
906 if ($hokAssertion === null) {
907 $hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', false);
908 }
909
910 if ($hokAssertion) {
911 // Holder-of-Key
913 if (\SimpleSAML\Utils\HTTP::isHTTPS()) {
914 if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) {
915 // extract certificate data (if this is a certificate)
916 $clientCert = $_SERVER['SSL_CLIENT_CERT'];
917 $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
918 if (preg_match($pattern, $clientCert, $matches)) {
919 // we have a client certificate from the browser which we add to the HoK assertion
920 $x509Certificate = new \SAML2\XML\ds\X509Certificate();
921 $x509Certificate->certificate = str_replace(array("\r", "\n", " "), '', $matches[1]);
922
923 $x509Data = new \SAML2\XML\ds\X509Data();
924 $x509Data->data[] = $x509Certificate;
925
926 $keyInfo = new \SAML2\XML\ds\KeyInfo();
927 $keyInfo->info[] = $x509Data;
928
929 $sc->SubjectConfirmationData->info[] = $keyInfo;
930 } else {
932 'Error creating HoK assertion: No valid client certificate provided during TLS handshake '.
933 'with IdP'
934 );
935 }
936 } else {
938 'Error creating HoK assertion: No client certificate provided during TLS handshake with IdP'
939 );
940 }
941 } else {
943 'Error creating HoK assertion: No HTTPS connection to IdP, but required for Holder-of-Key SSO'
944 );
945 }
946 } else {
947 // Bearer
949 }
950 $a->setSubjectConfirmation(array($sc));
951
952 // add attributes
953 if ($spMetadata->getBoolean('simplesaml.attributes', true)) {
955 $a->setAttributeNameFormat($attributeNameFormat);
957 $a->setAttributes($attributes);
958 }
959
960 // generate the NameID for the assertion
961 if (isset($state['saml:NameIDFormat'])) {
962 $nameIdFormat = $state['saml:NameIDFormat'];
963 } else {
964 $nameIdFormat = null;
965 }
966
967 if ($nameIdFormat === null || !isset($state['saml:NameID'][$nameIdFormat])) {
968 // either not set in request, or not set to a format we supply. Fall back to old generation method
969 $nameIdFormat = $spMetadata->getString('NameIDFormat', null);
970 if ($nameIdFormat === null) {
971 $nameIdFormat = $idpMetadata->getString('NameIDFormat', \SAML2\Constants::NAMEID_TRANSIENT);
972 }
973 }
974
975 if (isset($state['saml:NameID'][$nameIdFormat])) {
976 $nameId = $state['saml:NameID'][$nameIdFormat];
977 $nameId->Format = $nameIdFormat;
978 } else {
979 $spNameQualifier = $spMetadata->getString('SPNameQualifier', null);
980 if ($spNameQualifier === null) {
981 $spNameQualifier = $spMetadata->getString('entityid');
982 }
983
984 if ($nameIdFormat === \SAML2\Constants::NAMEID_TRANSIENT) {
985 // generate a random id
987 } else {
988 /* this code will end up generating either a fixed assigned id (via nameid.attribute)
989 or random id if not assigned/configured */
991 if ($nameIdValue === null) {
992 SimpleSAML\Logger::warning('Falling back to transient NameID.');
995 }
996 }
997
998 $nameId = new \SAML2\XML\saml\NameID();
999 $nameId->Format = $nameIdFormat;
1000 $nameId->value = $nameIdValue;
1001 $nameId->SPNameQualifier = $spNameQualifier;
1002 }
1003
1004 $state['saml:idp:NameID'] = $nameId;
1005
1006 $a->setNameId($nameId);
1007
1008 $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
1009 if ($encryptNameId === null) {
1010 $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
1011 }
1012 if ($encryptNameId) {
1014 }
1015
1016 return $a;
1017 }
1018
1019
1034 private static function encryptAssertion(
1037 \SAML2\Assertion $assertion
1038 ) {
1039
1040 $encryptAssertion = $spMetadata->getBoolean('assertion.encryption', null);
1041 if ($encryptAssertion === null) {
1042 $encryptAssertion = $idpMetadata->getBoolean('assertion.encryption', false);
1043 }
1044 if (!$encryptAssertion) {
1045 // we are _not_ encrypting this assertion, and are therefore done
1046 return $assertion;
1047 }
1048
1049
1050 $sharedKey = $spMetadata->getString('sharedkey', null);
1051 if ($sharedKey !== null) {
1052 $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
1053 $key->loadKey($sharedKey);
1054 } else {
1055 $keys = $spMetadata->getPublicKeys('encryption', true);
1056 $key = $keys[0];
1057 switch ($key['type']) {
1058 case 'X509Certificate':
1059 $pemKey = "-----BEGIN CERTIFICATE-----\n".
1060 chunk_split($key['X509Certificate'], 64).
1061 "-----END CERTIFICATE-----\n";
1062 break;
1063 default:
1064 throw new SimpleSAML_Error_Exception('Unsupported encryption key type: '.$key['type']);
1065 }
1066
1067 // extract the public key from the certificate for encryption
1068 $key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type' => 'public'));
1069 $key->loadKey($pemKey);
1070 }
1071
1072 $ea = new \SAML2\EncryptedAssertion();
1073 $ea->setAssertion($assertion, $key);
1074 return $ea;
1075 }
1076
1077
1088 private static function buildLogoutRequest(
1091 array $association,
1093 ) {
1094
1096 $lr->setRelayState($relayState);
1097 $lr->setSessionIndex($association['saml:SessionIndex']);
1098 $lr->setNameId($association['saml:NameID']);
1099
1100 $assertionLifetime = $spMetadata->getInteger('assertion.lifetime', null);
1101 if ($assertionLifetime === null) {
1102 $assertionLifetime = $idpMetadata->getInteger('assertion.lifetime', 300);
1103 }
1104 $lr->setNotOnOrAfter(time() + $assertionLifetime);
1105
1106 $encryptNameId = $spMetadata->getBoolean('nameid.encryption', null);
1107 if ($encryptNameId === null) {
1108 $encryptNameId = $idpMetadata->getBoolean('nameid.encryption', false);
1109 }
1110 if ($encryptNameId) {
1112 }
1113
1114 return $lr;
1115 }
1116
1117
1127 private static function buildResponse(
1130 $consumerURL
1131 ) {
1132
1133 $signResponse = $spMetadata->getBoolean('saml20.sign.response', null);
1134 if ($signResponse === null) {
1135 $signResponse = $idpMetadata->getBoolean('saml20.sign.response', true);
1136 }
1137
1138 $r = new \SAML2\Response();
1139
1140 $r->setIssuer($idpMetadata->getString('entityid'));
1141 $r->setDestination($consumerURL);
1142
1143 if ($signResponse) {
1145 }
1146
1147 return $r;
1148 }
1149}
$metadata['__DYNAMIC:1__']
$nameIdValue
$nameIdFormat
$spEntityId
$attributeNameFormat
$sc SubjectConfirmationData
if(!array_key_exists('stateid', $_REQUEST)) $state
Handle linkback() response from LinkedIn.
Definition: linkback.php:10
An exception for terminatinating execution or to throw for unit testing.
static getCurrentBinding()
Guess the current binding.
Definition: Binding.php:62
static getBinding($urn)
Retrieve a binding with the given URN.
Definition: Binding.php:28
const BINDING_HOK_SSO
The URN for the Holder-of-Key Web Browser SSO Profile binding.
Definition: Constants.php:50
const NAMEID_TRANSIENT
Transient NameID format.
Definition: Constants.php:195
const CM_BEARER
Bearer subject confirmation method.
Definition: Constants.php:55
const BINDING_HTTP_ARTIFACT
The URN for the HTTP-ARTIFACT binding.
Definition: Constants.php:35
const CM_HOK
Holder-of-Key subject confirmation method.
Definition: Constants.php:60
static info($string)
Definition: Logger.php:201
static warning($string)
Definition: Logger.php:179
static stats($string)
Definition: Logger.php:224
static error($string)
Definition: Logger.php:168
static debug($string)
Definition: Logger.php:213
static getModuleURL($resource, array $parameters=array())
Get absolute URL to a specified module resource.
Definition: Module.php:303
static getSecretSalt()
Retrieve the secret salt.
Definition: Config.php:49
static checkSessionCookie($retryURL=null)
Check for session cookie, and show missing-cookie page if it is missing.
Definition: HTTP.php:287
static generateID()
Generate a random identifier, ID_LENGTH bytes long.
Definition: Random.php:26
const EXCEPTION_HANDLER_FUNC
The index in the state array which contains the exception handler function.
Definition: State.php:69
const RESTART
The index in the state array which contains the restart URL.
Definition: State.php:57
static getInstance($instancename='simplesaml')
Get a configuration file by its instance name.
static loadFromArray($config, $location='[ARRAY]', $instance=null)
Loads a configuration from the given array.
log($default_level)
Print the exception to the log, by default with log level error.
Definition: Exception.php:236
static getByState(array &$state)
Retrieve the IdP "owning" the state.
Definition: IdP.php:152
static getMetadataHandler()
This function retrieves the current instance of the metadata handler.
static log($event, array $data=array())
Notify about an event.
Definition: Stats.php:71
static fromException(Exception $exception)
Create a SAML2 error from an exception.
Definition: Error.php:101
static sendLogoutResponse(SimpleSAML_IdP $idp, array $state)
Send a logout response.
Definition: SAML2.php:478
static receiveAuthnRequest(SimpleSAML_IdP $idp)
Receive an authentication request.
Definition: SAML2.php:248
static handleAuthError(SimpleSAML_Error_Exception $exception, array $state)
Handle authentication error.
Definition: SAML2.php:96
static buildAssertion(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, array &$state)
Build an assertion based on information in the metadata.
Definition: SAML2.php:845
static getAssociationConfig(SimpleSAML_IdP $idp, array $association)
Retrieve the metadata for the given SP association.
Definition: SAML2.php:651
static getLogoutURL(SimpleSAML_IdP $idp, array $association, $relayState)
Retrieve a logout URL for a given logout association.
Definition: SAML2.php:611
static receiveLogoutMessage(SimpleSAML_IdP $idp)
Receive a logout message.
Definition: SAML2.php:536
static buildLogoutRequest(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, array $association, $relayState)
Build a logout request based on information in the metadata.
Definition: SAML2.php:1088
static getAttributeNameFormat(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata)
Determine which NameFormat we should use for attributes.
Definition: SAML2.php:804
static sendLogoutRequest(SimpleSAML_IdP $idp, array $association, $relayState)
Send a logout request to a given association.
Definition: SAML2.php:442
static encodeAttributes(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, array $attributes)
Helper function for encoding attributes.
Definition: SAML2.php:723
static generateNameIdValue(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, array &$state)
Calculate the NameID value that should be used.
Definition: SAML2.php:671
static sendResponse(array $state)
Send a response to the SP.
Definition: SAML2.php:19
static encryptAssertion(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, \SAML2\Assertion $assertion)
Encrypt an assertion.
Definition: SAML2.php:1034
static getAssertionConsumerService(array $supportedBindings, SimpleSAML_Configuration $spMetadata, $AssertionConsumerServiceURL, $ProtocolBinding, $AssertionConsumerServiceIndex)
Find SP AssertionConsumerService based on parameter in AuthnRequest.
Definition: SAML2.php:162
static buildResponse(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, $consumerURL)
Build a authentication response based on information in the metadata.
Definition: SAML2.php:1127
static getResponseError(\SAML2\StatusResponse $response)
Retrieve the status code of a response as a sspmod_saml_Error.
Definition: Message.php:417
static addSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\SignedElement $element)
Add signature key and sender certificate to an element (Message or Assertion).
Definition: Message.php:20
static buildLogoutRequest(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Build a logout request based on information in the metadata.
Definition: Message.php:501
static buildLogoutResponse(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Build a logout response based on information in the metadata.
Definition: Message.php:521
static getEncryptionKey(SimpleSAML_Configuration $metadata)
Retrieve the encryption key for the given entity.
Definition: Message.php:829
static validateMessage(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Message $message)
Check signature on a SAML2 message if enabled.
Definition: Message.php:246
$key
Definition: croninfo.php:18
$r
Definition: example_031.php:79
$error
Definition: Error.php:17
$isPassive
Definition: login.php:18
$forceAuthn
Definition: login.php:17
if($format !==null) $name
Definition: metadata.php:146
$nameId
Definition: saml2-acs.php:138
catch(Exception $e) $message
$assertionLifetime
$lr
$binding
$relayState
if(!isset($associations[$assocId])) $association
if(!isset($_REQUEST['association'])) $assocId
$dst
$idpMetadata
$encryptNameId
$spMetadata
$bindings
$keys
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.
Definition: functions.php:406
Attribute-related utility methods.
$ret
Definition: parser.php:6
$idpEntityId
Definition: prp.php:12
$idp
Definition: prp.php:13
$attributes
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
$params
Definition: disable.php:11