ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
SAMLParser.php
Go to the documentation of this file.
1<?php
2
16{
22 private static $SAML1xProtocols = array(
23 'urn:oasis:names:tc:SAML:1.0:protocol',
24 'urn:oasis:names:tc:SAML:1.1:protocol',
25 );
26
32 private static $SAML20Protocols = array(
33 'urn:oasis:names:tc:SAML:2.0:protocol',
34 );
35
41 private $entityId;
42
54
64
71
79 private $organizationName = array();
80
88 private $organizationDisplayName = array();
89
96 private $organizationURL = array();
97
103 private $contacts = array();
104
108 private $scopes;
109
114
120
124 private $tags;
125
131 private $validators = array();
132
139
149 private function __construct(
150 \SAML2\XML\md\EntityDescriptor $entityElement,
151 $maxExpireTime,
152 array $validators = array(),
153 array $parentExtensions = array()
154 ) {
155 assert($maxExpireTime === null || is_int($maxExpireTime));
156
157 $this->spDescriptors = array();
158 $this->idpDescriptors = array();
159
160 $e = $entityElement->toXML();
161 $e = $e->ownerDocument->saveXML($e);
162 $this->entityDescriptor = base64_encode($e);
163 $this->entityId = $entityElement->entityID;
164
165 $expireTime = self::getExpireTime($entityElement, $maxExpireTime);
166
167 $this->validators = $validators;
168 $this->validators[] = $entityElement;
169
170 // process Extensions element, if it exists
171 $ext = self::processExtensions($entityElement, $parentExtensions);
172 $this->scopes = $ext['scope'];
173 $this->tags = $ext['tags'];
174 $this->entityAttributes = $ext['EntityAttributes'];
175 $this->registrationInfo = $ext['RegistrationInfo'];
176
177 // look over the RoleDescriptors
178 foreach ($entityElement->RoleDescriptor as $child) {
179 if ($child instanceof \SAML2\XML\md\SPSSODescriptor) {
180 $this->processSPSSODescriptor($child, $expireTime);
181 } elseif ($child instanceof \SAML2\XML\md\IDPSSODescriptor) {
182 $this->processIDPSSODescriptor($child, $expireTime);
183 } elseif ($child instanceof \SAML2\XML\md\AttributeAuthorityDescriptor) {
184 $this->processAttributeAuthorityDescriptor($child, $expireTime);
185 }
186 }
187
188 if ($entityElement->Organization) {
189 $this->processOrganization($entityElement->Organization);
190 }
191
192 if (!empty($entityElement->ContactPerson)) {
193 foreach ($entityElement->ContactPerson as $contact) {
194 $this->processContactPerson($contact);
195 }
196 }
197 }
198
199
208 public static function parseFile($file)
209 {
211
212 try {
214 } catch (\Exception $e) {
215 throw new Exception('Failed to read XML from file: '.$file);
216 }
217
218 return self::parseDocument($doc);
219 }
220
221
230 public static function parseString($metadata)
231 {
232 try {
234 } catch (\Exception $e) {
235 throw new Exception('Failed to parse XML string.');
236 }
237
238 return self::parseDocument($doc);
239 }
240
241
249 public static function parseDocument($document)
250 {
251 assert($document instanceof DOMDocument);
252
253 $entityElement = self::findEntityDescriptor($document);
254
255 return self::parseElement($entityElement);
256 }
257
258
267 public static function parseElement($entityElement)
268 {
269 assert($entityElement instanceof \SAML2\XML\md\EntityDescriptor);
270 return new SimpleSAML_Metadata_SAMLParser($entityElement, null, array());
271 }
272
273
285 public static function parseDescriptorsFile($file)
286 {
287 if ($file === null) {
288 throw new Exception('Cannot open file NULL. File name not specified.');
289 }
290
292
293 try {
295 } catch (\Exception $e) {
296 throw new Exception('Failed to read XML from file: '.$file);
297 }
298
299 if ($doc->documentElement === null) {
300 throw new Exception('Opened file is not an XML document: '.$file);
301 }
302
303 return self::parseDescriptorsElement($doc->documentElement);
304 }
305
306
318 public static function parseDescriptorsString($string)
319 {
320 try {
322 } catch (\Exception $e) {
323 throw new Exception('Failed to parse XML string.');
324 }
325
326 return self::parseDescriptorsElement($doc->documentElement);
327 }
328
329
341 public static function parseDescriptorsElement(DOMElement $element = null)
342 {
343 if ($element === null) {
344 throw new Exception('Document was empty.');
345 }
346
347 if (SimpleSAML\Utils\XML::isDOMNodeOfType($element, 'EntityDescriptor', '@md') === true) {
348 return self::processDescriptorsElement(new \SAML2\XML\md\EntityDescriptor($element));
349 } elseif (SimpleSAML\Utils\XML::isDOMNodeOfType($element, 'EntitiesDescriptor', '@md') === true) {
350 return self::processDescriptorsElement(new \SAML2\XML\md\EntitiesDescriptor($element));
351 } else {
352 throw new Exception('Unexpected root node: ['.$element->namespaceURI.']:'.$element->localName);
353 }
354 }
355
356
369 private static function processDescriptorsElement(
370 $element,
371 $maxExpireTime = null,
372 array $validators = array(),
373 array $parentExtensions = array()
374 ) {
375 assert($maxExpireTime === null || is_int($maxExpireTime));
376
377 if ($element instanceof \SAML2\XML\md\EntityDescriptor) {
378 $ret = new SimpleSAML_Metadata_SAMLParser($element, $maxExpireTime, $validators, $parentExtensions);
379 $ret = array($ret->getEntityId() => $ret);
381 return $ret;
382 }
383
384 assert($element instanceof \SAML2\XML\md\EntitiesDescriptor);
385
386 $extensions = self::processExtensions($element, $parentExtensions);
387 $expTime = self::getExpireTime($element, $maxExpireTime);
388
389 $validators[] = $element;
390
391 $ret = array();
392 foreach ($element->children as $child) {
393 $ret += self::processDescriptorsElement($child, $expTime, $validators, $extensions);
394 }
395
396 return $ret;
397 }
398
399
412 private static function getExpireTime($element, $maxExpireTime)
413 {
414 // validUntil may be null
415 $expire = $element->validUntil;
416
417 if ($maxExpireTime !== null && ($expire === null || $maxExpireTime < $expire)) {
418 $expire = $maxExpireTime;
419 }
420
421 return $expire;
422 }
423
424
430 public function getEntityId()
431 {
432 return $this->entityId;
433 }
434
435
436 private function getMetadataCommon()
437 {
438 $ret = array();
439 $ret['entityid'] = $this->entityId;
440 $ret['entityDescriptor'] = $this->entityDescriptor;
441
442 // add organizational metadata
443 if (!empty($this->organizationName)) {
444 $ret['description'] = $this->organizationName;
445 $ret['OrganizationName'] = $this->organizationName;
446 }
447 if (!empty($this->organizationDisplayName)) {
449 $ret['OrganizationDisplayName'] = $this->organizationDisplayName;
450 }
451 if (!empty($this->organizationURL)) {
453 $ret['OrganizationURL'] = $this->organizationURL;
454 }
455
456 //add contact metadata
457 $ret['contacts'] = $this->contacts;
458
459 return $ret;
460 }
461
462
469 private function addExtensions(array &$metadata, array $roleDescriptor)
470 {
471 assert(array_key_exists('scope', $roleDescriptor));
472 assert(array_key_exists('tags', $roleDescriptor));
473
474 $scopes = array_merge($this->scopes, array_diff($roleDescriptor['scope'], $this->scopes));
475 if (!empty($scopes)) {
476 $metadata['scope'] = $scopes;
477 }
478
479 $tags = array_merge($this->tags, array_diff($roleDescriptor['tags'], $this->tags));
480 if (!empty($tags)) {
481 $metadata['tags'] = $tags;
482 }
483
484
485 if (!empty($this->registrationInfo)) {
486 $metadata['RegistrationInfo'] = $this->registrationInfo;
487 }
488
489 if (!empty($this->entityAttributes)) {
490 $metadata['EntityAttributes'] = $this->entityAttributes;
491
492 // check for entity categories
493 if (SimpleSAML\Utils\Config\Metadata::isHiddenFromDiscovery($metadata)) {
494 $metadata['hide.from.discovery'] = true;
495 }
496 }
497
498 if (!empty($roleDescriptor['UIInfo'])) {
499 $metadata['UIInfo'] = $roleDescriptor['UIInfo'];
500 }
501
502 if (!empty($roleDescriptor['DiscoHints'])) {
503 $metadata['DiscoHints'] = $roleDescriptor['DiscoHints'];
504 }
505 }
506
507
520 public function getMetadata1xSP()
521 {
522 $ret = $this->getMetadataCommon();
523 $ret['metadata-set'] = 'shib13-sp-remote';
524
525
526 // find SP information which supports one of the SAML 1.x protocols
527 $spd = $this->getSPDescriptors(self::$SAML1xProtocols);
528 if (count($spd) === 0) {
529 return null;
530 }
531
532 // we currently only look at the first SPDescriptor which supports SAML 1.x
533 $spd = $spd[0];
534
535 // add expire time to metadata
536 if (array_key_exists('expire', $spd)) {
537 $ret['expire'] = $spd['expire'];
538 }
539
540 // find the assertion consumer service endpoints
541 $ret['AssertionConsumerService'] = $spd['AssertionConsumerService'];
542
543 // add the list of attributes the SP should receive
544 if (array_key_exists('attributes', $spd)) {
545 $ret['attributes'] = $spd['attributes'];
546 }
547 if (array_key_exists('attributes.required', $spd)) {
548 $ret['attributes.required'] = $spd['attributes.required'];
549 }
550 if (array_key_exists('attributes.NameFormat', $spd)) {
551 $ret['attributes.NameFormat'] = $spd['attributes.NameFormat'];
552 }
553
554 // add name & description
555 if (array_key_exists('name', $spd)) {
556 $ret['name'] = $spd['name'];
557 }
558 if (array_key_exists('description', $spd)) {
559 $ret['description'] = $spd['description'];
560 }
561
562 // add public keys
563 if (!empty($spd['keys'])) {
564 $ret['keys'] = $spd['keys'];
565 }
566
567 // add extensions
568 $this->addExtensions($ret, $spd);
569
570 // prioritize mdui:DisplayName as the name if available
571 if (!empty($ret['UIInfo']['DisplayName'])) {
572 $ret['name'] = $ret['UIInfo']['DisplayName'];
573 }
574
575 return $ret;
576 }
577
578
594 public function getMetadata1xIdP()
595 {
596 $ret = $this->getMetadataCommon();
597 $ret['metadata-set'] = 'shib13-idp-remote';
598
599 // find IdP information which supports the SAML 1.x protocol
600 $idp = $this->getIdPDescriptors(self::$SAML1xProtocols);
601 if (count($idp) === 0) {
602 return null;
603 }
604
605 // we currently only look at the first IDP descriptor which supports SAML 1.x
606 $idp = $idp[0];
607
608 // fdd expire time to metadata
609 if (array_key_exists('expire', $idp)) {
610 $ret['expire'] = $idp['expire'];
611 }
612
613 // find the SSO service endpoints
614 $ret['SingleSignOnService'] = $idp['SingleSignOnService'];
615
616 // find the ArtifactResolutionService endpoint
617 $ret['ArtifactResolutionService'] = $idp['ArtifactResolutionService'];
618
619 // add public keys
620 if (!empty($idp['keys'])) {
621 $ret['keys'] = $idp['keys'];
622 }
623
624 // add extensions
625 $this->addExtensions($ret, $idp);
626
627 // prioritize mdui:DisplayName as the name if available
628 if (!empty($ret['UIInfo']['DisplayName'])) {
629 $ret['name'] = $ret['UIInfo']['DisplayName'];
630 }
631
632 return $ret;
633 }
634
635
650 public function getMetadata20SP()
651 {
652 $ret = $this->getMetadataCommon();
653 $ret['metadata-set'] = 'saml20-sp-remote';
654
655 // find SP information which supports the SAML 2.0 protocol
656 $spd = $this->getSPDescriptors(self::$SAML20Protocols);
657 if (count($spd) === 0) {
658 return null;
659 }
660
661 // we currently only look at the first SPDescriptor which supports SAML 2.0
662 $spd = $spd[0];
663
664 // add expire time to metadata
665 if (array_key_exists('expire', $spd)) {
666 $ret['expire'] = $spd['expire'];
667 }
668
669 // find the assertion consumer service endpoints
670 $ret['AssertionConsumerService'] = $spd['AssertionConsumerService'];
671
672
673 // find the single logout service endpoint
674 $ret['SingleLogoutService'] = $spd['SingleLogoutService'];
675
676
677 // find the NameIDFormat. This may not exist
678 if (count($spd['nameIDFormats']) > 0) {
679 // SimpleSAMLphp currently only supports a single NameIDFormat pr. SP. We use the first one
680 $ret['NameIDFormat'] = $spd['nameIDFormats'][0];
681 }
682
683 // add the list of attributes the SP should receive
684 if (array_key_exists('attributes', $spd)) {
685 $ret['attributes'] = $spd['attributes'];
686 }
687 if (array_key_exists('attributes.required', $spd)) {
688 $ret['attributes.required'] = $spd['attributes.required'];
689 }
690 if (array_key_exists('attributes.NameFormat', $spd)) {
691 $ret['attributes.NameFormat'] = $spd['attributes.NameFormat'];
692 }
693 if (array_key_exists('attributes.index', $spd)) {
694 $ret['attributes.index'] = $spd['attributes.index'];
695 }
696 if (array_key_exists('attributes.isDefault', $spd)) {
697 $ret['attributes.isDefault'] = $spd['attributes.isDefault'];
698 }
699
700 // add name & description
701 if (array_key_exists('name', $spd)) {
702 $ret['name'] = $spd['name'];
703 }
704 if (array_key_exists('description', $spd)) {
705 $ret['description'] = $spd['description'];
706 }
707
708 // add public keys
709 if (!empty($spd['keys'])) {
710 $ret['keys'] = $spd['keys'];
711 }
712
713 // add validate.authnrequest
714 if (array_key_exists('AuthnRequestsSigned', $spd)) {
715 $ret['validate.authnrequest'] = $spd['AuthnRequestsSigned'];
716 }
717
718 // add saml20.sign.assertion
719 if (array_key_exists('WantAssertionsSigned', $spd)) {
720 $ret['saml20.sign.assertion'] = $spd['WantAssertionsSigned'];
721 }
722
723 // add extensions
724 $this->addExtensions($ret, $spd);
725
726 // prioritize mdui:DisplayName as the name if available
727 if (!empty($ret['UIInfo']['DisplayName'])) {
728 $ret['name'] = $ret['UIInfo']['DisplayName'];
729 }
730
731 return $ret;
732 }
733
734
753 public function getMetadata20IdP()
754 {
755 $ret = $this->getMetadataCommon();
756 $ret['metadata-set'] = 'saml20-idp-remote';
757
758 // find IdP information which supports the SAML 2.0 protocol
759 $idp = $this->getIdPDescriptors(self::$SAML20Protocols);
760 if (count($idp) === 0) {
761 return null;
762 }
763
764 // we currently only look at the first IDP descriptor which supports SAML 2.0
765 $idp = $idp[0];
766
767 // add expire time to metadata
768 if (array_key_exists('expire', $idp)) {
769 $ret['expire'] = $idp['expire'];
770 }
771
772 // enable redirect.sign if WantAuthnRequestsSigned is enabled
773 if ($idp['WantAuthnRequestsSigned']) {
774 $ret['sign.authnrequest'] = true;
775 }
776
777 // find the SSO service endpoint
778 $ret['SingleSignOnService'] = $idp['SingleSignOnService'];
779
780 // find the single logout service endpoint
781 $ret['SingleLogoutService'] = $idp['SingleLogoutService'];
782
783 // find the ArtifactResolutionService endpoint
784 $ret['ArtifactResolutionService'] = $idp['ArtifactResolutionService'];
785
786 // add supported nameIDFormats
787 $ret['NameIDFormats'] = $idp['nameIDFormats'];
788
789 // add public keys
790 if (!empty($idp['keys'])) {
791 $ret['keys'] = $idp['keys'];
792 }
793
794 // add extensions
795 $this->addExtensions($ret, $idp);
796
797 // prioritize mdui:DisplayName as the name if available
798 if (!empty($ret['UIInfo']['DisplayName'])) {
799 $ret['name'] = $ret['UIInfo']['DisplayName'];
800 }
801
802 return $ret;
803 }
804
805
811 public function getAttributeAuthorities()
812 {
814 }
815
816
831 private static function parseRoleDescriptorType(\SAML2\XML\md\RoleDescriptor $element, $expireTime)
832 {
833 assert($expireTime === null || is_int($expireTime));
834
835 $ret = array();
836
837 $expireTime = self::getExpireTime($element, $expireTime);
838
839 if ($expireTime !== null) {
840 // we got an expired timestamp, either from this element or one of the parent elements
841 $ret['expire'] = $expireTime;
842 }
843
844 $ret['protocols'] = $element->protocolSupportEnumeration;
845
846 // process KeyDescriptor elements
847 $ret['keys'] = array();
848 foreach ($element->KeyDescriptor as $kd) {
850 if ($key !== null) {
851 $ret['keys'][] = $key;
852 }
853 }
854
855 $ext = self::processExtensions($element);
856 $ret['scope'] = $ext['scope'];
857 $ret['tags'] = $ext['tags'];
858 $ret['EntityAttributes'] = $ext['EntityAttributes'];
859 $ret['UIInfo'] = $ext['UIInfo'];
860 $ret['DiscoHints'] = $ext['DiscoHints'];
861
862 return $ret;
863 }
864
865
882 private static function parseSSODescriptor(\SAML2\XML\md\SSODescriptorType $element, $expireTime)
883 {
884 assert($expireTime === null || is_int($expireTime));
885
886 $sd = self::parseRoleDescriptorType($element, $expireTime);
887
888 // find all SingleLogoutService elements
889 $sd['SingleLogoutService'] = self::extractEndpoints($element->SingleLogoutService);
890
891 // find all ArtifactResolutionService elements
892 $sd['ArtifactResolutionService'] = self::extractEndpoints($element->ArtifactResolutionService);
893
894
895 // process NameIDFormat elements
896 $sd['nameIDFormats'] = $element->NameIDFormat;
897
898 return $sd;
899 }
900
901
909 private function processSPSSODescriptor(\SAML2\XML\md\SPSSODescriptor $element, $expireTime)
910 {
911 assert($expireTime === null || is_int($expireTime));
912
913 $sp = self::parseSSODescriptor($element, $expireTime);
914
915 // find all AssertionConsumerService elements
916 $sp['AssertionConsumerService'] = self::extractEndpoints($element->AssertionConsumerService);
917
918 // find all the attributes and SP name...
919 $attcs = $element->AttributeConsumingService;
920 if (count($attcs) > 0) {
922 }
923
924 // check AuthnRequestsSigned
925 if ($element->AuthnRequestsSigned !== null) {
926 $sp['AuthnRequestsSigned'] = $element->AuthnRequestsSigned;
927 }
928
929 // check WantAssertionsSigned
930 if ($element->WantAssertionsSigned !== null) {
931 $sp['WantAssertionsSigned'] = $element->WantAssertionsSigned;
932 }
933
934 $this->spDescriptors[] = $sp;
935 }
936
937
945 private function processIDPSSODescriptor(\SAML2\XML\md\IDPSSODescriptor $element, $expireTime)
946 {
947 assert($expireTime === null || is_int($expireTime));
948
949 $idp = self::parseSSODescriptor($element, $expireTime);
950
951 // find all SingleSignOnService elements
952 $idp['SingleSignOnService'] = self::extractEndpoints($element->SingleSignOnService);
953
954 if ($element->WantAuthnRequestsSigned) {
955 $idp['WantAuthnRequestsSigned'] = true;
956 } else {
957 $idp['WantAuthnRequestsSigned'] = false;
958 }
959
960 $this->idpDescriptors[] = $idp;
961 }
962
963
972 \SAML2\XML\md\AttributeAuthorityDescriptor $element,
973 $expireTime
974 ) {
975 assert($expireTime === null || is_int($expireTime));
976
977 $aad = self::parseRoleDescriptorType($element, $expireTime);
978 $aad['entityid'] = $this->entityId;
979 $aad['metadata-set'] = 'attributeauthority-remote';
980
981 $aad['AttributeService'] = self::extractEndpoints($element->AttributeService);
982 $aad['AssertionIDRequestService'] = self::extractEndpoints($element->AssertionIDRequestService);
983 $aad['NameIDFormat'] = $element->NameIDFormat;
984
985 $this->attributeAuthorityDescriptors[] = $aad;
986 }
987
988
998 private static function processExtensions($element, $parentExtensions = array())
999 {
1000 $ret = array(
1001 'scope' => array(),
1002 'tags' => array(),
1003 'EntityAttributes' => array(),
1004 'RegistrationInfo' => array(),
1005 'UIInfo' => array(),
1006 'DiscoHints' => array(),
1007 );
1008
1009 // Some extensions may get inherited from a parent element
1010 if (($element instanceof \SAML2\XML\md\EntityDescriptor || $element instanceof \SAML2\XML\md\EntitiesDescriptor)
1011 && !empty($parentExtensions['RegistrationInfo'])) {
1012 $ret['RegistrationInfo'] = $parentExtensions['RegistrationInfo'];
1013 }
1014
1015 foreach ($element->Extensions as $e) {
1016 if ($e instanceof \SAML2\XML\shibmd\Scope) {
1017 $ret['scope'][] = $e->scope;
1018 continue;
1019 }
1020
1021 // Entity Attributes are only allowed at entity level extensions and not at RoleDescriptor level
1022 if ($element instanceof \SAML2\XML\md\EntityDescriptor ||
1023 $element instanceof \SAML2\XML\md\EntitiesDescriptor) {
1024 if ($e instanceof \SAML2\XML\mdrpi\RegistrationInfo) {
1025 // Registration Authority cannot be overridden (warn only if override attempts to change the value)
1026 if (isset($ret['RegistrationInfo']['registrationAuthority'])
1027 && $ret['RegistrationInfo']['registrationAuthority'] !== $e->registrationAuthority) {
1028 SimpleSAML\Logger::warning('Invalid attempt to override registrationAuthority \''
1029 . $ret['RegistrationInfo']['registrationAuthority'] . "' with '{$e->registrationAuthority}'");
1030 } else {
1031 $ret['RegistrationInfo']['registrationAuthority'] = $e->registrationAuthority;
1032 }
1033 }
1034 if ($e instanceof \SAML2\XML\mdattr\EntityAttributes && !empty($e->children)) {
1035 foreach ($e->children as $attr) {
1036 // only saml:Attribute are currently supported here. The specifications also allows
1037 // saml:Assertions, which more complex processing
1038 if ($attr instanceof \SAML2\XML\saml\Attribute) {
1039 if (empty($attr->Name) || empty($attr->AttributeValue)) {
1040 continue;
1041 }
1042
1043 // attribute names that is not URI is prefixed as this: '{nameformat}name'
1044 $name = $attr->Name;
1045 if (empty($attr->NameFormat)) {
1046 $name = '{'.\SAML2\Constants::NAMEFORMAT_UNSPECIFIED.'}'.$attr->Name;
1047 } elseif ($attr->NameFormat !== 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri') {
1048 $name = '{'.$attr->NameFormat.'}'.$attr->Name;
1049 }
1050
1051 $values = array();
1052 foreach ($attr->AttributeValue as $attrvalue) {
1053 $values[] = $attrvalue->getString();
1054 }
1055
1056 $ret['EntityAttributes'][$name] = $values;
1057 }
1058 }
1059 }
1060 }
1061
1062 // UIInfo elements are only allowed at RoleDescriptor level extensions
1063 if ($element instanceof \SAML2\XML\md\RoleDescriptor) {
1064 if ($e instanceof \SAML2\XML\mdui\UIInfo) {
1065 $ret['UIInfo']['DisplayName'] = $e->DisplayName;
1066 $ret['UIInfo']['Description'] = $e->Description;
1067 $ret['UIInfo']['InformationURL'] = $e->InformationURL;
1068 $ret['UIInfo']['PrivacyStatementURL'] = $e->PrivacyStatementURL;
1069
1070 foreach ($e->Keywords as $uiItem) {
1071 if (!($uiItem instanceof \SAML2\XML\mdui\Keywords)
1072 || empty($uiItem->Keywords)
1073 || empty($uiItem->lang)
1074 ) {
1075 continue;
1076 }
1077 $ret['UIInfo']['Keywords'][$uiItem->lang] = $uiItem->Keywords;
1078 }
1079 foreach ($e->Logo as $uiItem) {
1080 if (!($uiItem instanceof \SAML2\XML\mdui\Logo)
1081 || empty($uiItem->url)
1082 || empty($uiItem->height)
1083 || empty($uiItem->width)
1084 ) {
1085 continue;
1086 }
1087 $logo = array(
1088 'url' => $uiItem->url,
1089 'height' => $uiItem->height,
1090 'width' => $uiItem->width,
1091 );
1092 if (!empty($uiItem->lang)) {
1093 $logo['lang'] = $uiItem->lang;
1094 }
1095 $ret['UIInfo']['Logo'][] = $logo;
1096 }
1097 }
1098 }
1099
1100 // DiscoHints elements are only allowed at IDPSSODescriptor level extensions
1101 if ($element instanceof \SAML2\XML\md\IDPSSODescriptor) {
1102 if ($e instanceof \SAML2\XML\mdui\DiscoHints) {
1103 $ret['DiscoHints']['IPHint'] = $e->IPHint;
1104 $ret['DiscoHints']['DomainHint'] = $e->DomainHint;
1105 $ret['DiscoHints']['GeolocationHint'] = $e->GeolocationHint;
1106 }
1107 }
1108
1109 if (!($e instanceof \SAML2\XML\Chunk)) {
1110 continue;
1111 }
1112
1113 if ($e->localName === 'Attribute' && $e->namespaceURI === \SAML2\Constants::NS_SAML) {
1114 $attribute = $e->xml;
1115
1116 $name = $attribute->getAttribute('Name');
1117 $values = array_map(
1118 array('SimpleSAML\Utils\XML', 'getDOMText'),
1119 SimpleSAML\Utils\XML::getDOMChildren($attribute, 'AttributeValue', '@saml2')
1120 );
1121
1122 if ($name === 'tags') {
1123 foreach ($values as $tagname) {
1124 if (!empty($tagname)) {
1125 $ret['tags'][] = $tagname;
1126 }
1127 }
1128 }
1129 }
1130 }
1131 return $ret;
1132 }
1133
1134
1140 private function processOrganization(\SAML2\XML\md\Organization $element)
1141 {
1142 $this->organizationName = $element->OrganizationName;
1143 $this->organizationDisplayName = $element->OrganizationDisplayName;
1144 $this->organizationURL = $element->OrganizationURL;
1145 }
1146
1147
1154 private function processContactPerson(\SAML2\XML\md\ContactPerson $element)
1155 {
1156 $contactPerson = array();
1157 if (!empty($element->contactType)) {
1158 $contactPerson['contactType'] = $element->contactType;
1159 }
1160 if (!empty($element->Company)) {
1161 $contactPerson['company'] = $element->Company;
1162 }
1163 if (!empty($element->GivenName)) {
1164 $contactPerson['givenName'] = $element->GivenName;
1165 }
1166 if (!empty($element->SurName)) {
1167 $contactPerson['surName'] = $element->SurName;
1168 }
1169 if (!empty($element->EmailAddress)) {
1170 $contactPerson['emailAddress'] = $element->EmailAddress;
1171 }
1172 if (!empty($element->TelephoneNumber)) {
1173 $contactPerson['telephoneNumber'] = $element->TelephoneNumber;
1174 }
1175 if (!empty($contactPerson)) {
1176 $this->contacts[] = $contactPerson;
1177 }
1178 }
1179
1180
1187 private static function parseAttributeConsumerService(\SAML2\XML\md\AttributeConsumingService $element, &$sp)
1188 {
1189 assert(is_array($sp));
1190
1191 $sp['name'] = $element->ServiceName;
1192 $sp['description'] = $element->ServiceDescription;
1193
1194 $format = null;
1195 $sp['attributes'] = array();
1196 $sp['attributes.required'] = array();
1197 foreach ($element->RequestedAttribute as $child) {
1198 $attrname = $child->Name;
1199 $sp['attributes'][] = $attrname;
1200
1201 if ($child->isRequired !== null && $child->isRequired === true) {
1202 $sp['attributes.required'][] = $attrname;
1203 }
1204
1205 if ($child->NameFormat !== null) {
1206 $attrformat = $child->NameFormat;
1207 } else {
1209 }
1210
1211 if ($format === null) {
1212 $format = $attrformat;
1213 } elseif ($format !== $attrformat) {
1215 }
1216 }
1217
1218 if (empty($sp['attributes'])) {
1219 // a really invalid configuration: all AttributeConsumingServices should have one or more attributes
1220 unset($sp['attributes']);
1221 }
1222 if (empty($sp['attributes.required'])) {
1223 unset($sp['attributes.required']);
1224 }
1225
1226 if ($format !== \SAML2\Constants::NAMEFORMAT_UNSPECIFIED && $format !== null) {
1227 $sp['attributes.NameFormat'] = $format;
1228 }
1229 }
1230
1231
1246 private static function parseGenericEndpoint(\SAML2\XML\md\EndpointType $element)
1247 {
1248 $ep = array();
1249
1250 $ep['Binding'] = $element->Binding;
1251 $ep['Location'] = $element->Location;
1252
1253 if ($element->ResponseLocation !== null) {
1254 $ep['ResponseLocation'] = $element->ResponseLocation;
1255 }
1256
1257 if ($element instanceof \SAML2\XML\md\IndexedEndpointType) {
1258 $ep['index'] = $element->index;
1259
1260 if ($element->isDefault !== null) {
1261 $ep['isDefault'] = $element->isDefault;
1262 }
1263 }
1264
1265 return $ep;
1266 }
1267
1268
1276 private static function extractEndpoints(array $endpoints)
1277 {
1278 $ret = array();
1279 foreach ($endpoints as $ep) {
1281 }
1282
1283 return $ret;
1284 }
1285
1286
1301 private static function parseKeyDescriptor(\SAML2\XML\md\KeyDescriptor $kd)
1302 {
1303 $r = array();
1304
1305 if ($kd->use === 'encryption') {
1306 $r['encryption'] = true;
1307 $r['signing'] = false;
1308 } elseif ($kd->use === 'signing') {
1309 $r['encryption'] = false;
1310 $r['signing'] = true;
1311 } else {
1312 $r['encryption'] = true;
1313 $r['signing'] = true;
1314 }
1315
1316 $keyInfo = $kd->KeyInfo;
1317
1318 foreach ($keyInfo->info as $i) {
1319 if ($i instanceof \SAML2\XML\ds\X509Data) {
1320 foreach ($i->data as $d) {
1321 if ($d instanceof \SAML2\XML\ds\X509Certificate) {
1322 $r['type'] = 'X509Certificate';
1323 $r['X509Certificate'] = $d->certificate;
1324 return $r;
1325 }
1326 }
1327 }
1328 }
1329
1330 return null;
1331 }
1332
1333
1341 private function getSPDescriptors($protocols)
1342 {
1343 assert(is_array($protocols));
1344
1345 $ret = array();
1346
1347 foreach ($this->spDescriptors as $spd) {
1348 $sharedProtocols = array_intersect($protocols, $spd['protocols']);
1349 if (count($sharedProtocols) > 0) {
1350 $ret[] = $spd;
1351 }
1352 }
1353
1354 return $ret;
1355 }
1356
1357
1365 private function getIdPDescriptors($protocols)
1366 {
1367 assert(is_array($protocols));
1368
1369 $ret = array();
1370
1371 foreach ($this->idpDescriptors as $idpd) {
1372 $sharedProtocols = array_intersect($protocols, $idpd['protocols']);
1373 if (count($sharedProtocols) > 0) {
1374 $ret[] = $idpd;
1375 }
1376 }
1377
1378 return $ret;
1379 }
1380
1381
1393 private static function findEntityDescriptor($doc)
1394 {
1395 assert($doc instanceof DOMDocument);
1396
1397 // find the EntityDescriptor DOMElement. This should be the first (and only) child of the DOMDocument
1398 $ed = $doc->documentElement;
1399
1400 if ($ed === null) {
1401 throw new Exception('Failed to load SAML metadata from empty XML document.');
1402 }
1403
1404 if (SimpleSAML\Utils\XML::isDOMNodeOfType($ed, 'EntityDescriptor', '@md') === false) {
1405 throw new Exception('Expected first element in the metadata document to be an EntityDescriptor element.');
1406 }
1407
1408 return new \SAML2\XML\md\EntityDescriptor($ed);
1409 }
1410
1411
1422 {
1423 foreach ($certificates as $cert) {
1424 assert(is_string($cert));
1425 $certFile = \SimpleSAML\Utils\Config::getCertPath($cert);
1426 if (!file_exists($certFile)) {
1427 throw new Exception(
1428 'Could not find certificate file ['.$certFile.'], which is needed to validate signature'
1429 );
1430 }
1431 $certData = file_get_contents($certFile);
1432
1433 foreach ($this->validators as $validator) {
1434 $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'public'));
1435 $key->loadKey($certData);
1436 try {
1437 if ($validator->validate($key)) {
1438 return true;
1439 }
1440 } catch (Exception $e) {
1441 // this certificate did not sign this element, skip
1442 }
1443 }
1444 }
1445 SimpleSAML\Logger::debug('Could not validate signature');
1446 return false;
1447 }
1448
1449
1459 public function validateFingerprint($fingerprint)
1460 {
1461 assert(is_string($fingerprint));
1462
1463 $fingerprint = strtolower(str_replace(":", "", $fingerprint));
1464
1465 $candidates = array();
1466 foreach ($this->validators as $validator) {
1467 foreach ($validator->getValidatingCertificates() as $cert) {
1468 $fp = strtolower(sha1(base64_decode($cert)));
1469 $candidates[] = $fp;
1470 if ($fp === $fingerprint) {
1471 return true;
1472 }
1473 }
1474 }
1475 SimpleSAML\Logger::debug('Fingerprint was ['.$fingerprint.'] not one of ['.join(', ', $candidates).']');
1476 return false;
1477 }
1478}
$metadata['__DYNAMIC:1__']
An exception for terminatinating execution or to throw for unit testing.
const NAMEFORMAT_UNSPECIFIED
The interpretation of the attribute name is left to individual implementations.
Definition: Constants.php:146
static warning($string)
Definition: Logger.php:177
static debug($string)
Definition: Logger.php:211
static getCertPath($path)
Resolves a path that may be relative to the cert-directory.
Definition: Config.php:22
static fetch($url, $context=array(), $getHeaders=false)
Helper function to retrieve a file or URL with proxy support, also supporting proxy basic authorizati...
Definition: HTTP.php:408
This is class for parsing of SAML 1.x and SAML 2.0 metadata.
Definition: SAMLParser.php:16
getMetadata1xSP()
This function returns the metadata for SAML 1.x SPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:520
getIdPDescriptors($protocols)
This function finds IdP descriptors which supports one of the given protocols.
getAttributeAuthorities()
Retrieve AttributeAuthorities from the metadata.
Definition: SAMLParser.php:811
static parseSSODescriptor(\SAML2\XML\md\SSODescriptorType $element, $expireTime)
This function extracts metadata from a SSODescriptor element.
Definition: SAMLParser.php:882
static parseString($metadata)
This function parses a string which contains XML encoded metadata.
Definition: SAMLParser.php:230
static parseDescriptorsElement(DOMElement $element=null)
This function parses a DOMElement which represents either an EntityDescriptor element or an EntitiesD...
Definition: SAMLParser.php:341
static processExtensions($element, $parentExtensions=array())
Parse an Extensions element.
Definition: SAMLParser.php:998
__construct(\SAML2\XML\md\EntityDescriptor $entityElement, $maxExpireTime, array $validators=array(), array $parentExtensions=array())
This is the constructor for the SAMLParser class.
Definition: SAMLParser.php:149
static parseKeyDescriptor(\SAML2\XML\md\KeyDescriptor $kd)
This function parses a KeyDescriptor element.
getMetadata20SP()
This function returns the metadata for SAML 2.0 SPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:650
static findEntityDescriptor($doc)
This function locates the EntityDescriptor node in a DOMDocument.
static parseDescriptorsString($string)
This function parses a string with XML data.
Definition: SAMLParser.php:318
static parseGenericEndpoint(\SAML2\XML\md\EndpointType $element)
This function is a generic endpoint element parser.
static getExpireTime($element, $maxExpireTime)
Determine how long a given element can be cached.
Definition: SAMLParser.php:412
static parseDocument($document)
This function parses a DOMDocument which is assumed to contain a single EntityDescriptor element.
Definition: SAMLParser.php:249
static parseDescriptorsFile($file)
This function parses a file where the root node is either an EntityDescriptor element or an EntitiesD...
Definition: SAMLParser.php:285
getMetadata20IdP()
This function returns the metadata for SAML 2.0 IdPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:753
addExtensions(array &$metadata, array $roleDescriptor)
Add data parsed from extensions to metadata.
Definition: SAMLParser.php:469
processSPSSODescriptor(\SAML2\XML\md\SPSSODescriptor $element, $expireTime)
This function extracts metadata from a SPSSODescriptor element.
Definition: SAMLParser.php:909
validateSignature($certificates)
If this EntityDescriptor was signed this function use the public key to check the signature.
static extractEndpoints(array $endpoints)
Extract generic endpoints.
processIDPSSODescriptor(\SAML2\XML\md\IDPSSODescriptor $element, $expireTime)
This function extracts metadata from a IDPSSODescriptor element.
Definition: SAMLParser.php:945
processAttributeAuthorityDescriptor(\SAML2\XML\md\AttributeAuthorityDescriptor $element, $expireTime)
This function extracts metadata from a AttributeAuthorityDescriptor element.
Definition: SAMLParser.php:971
getSPDescriptors($protocols)
This function finds SP descriptors which supports one of the given protocols.
static parseRoleDescriptorType(\SAML2\XML\md\RoleDescriptor $element, $expireTime)
Parse a RoleDescriptorType element.
Definition: SAMLParser.php:831
getEntityId()
This function returns the entity id of this parsed entity.
Definition: SAMLParser.php:430
processContactPerson(\SAML2\XML\md\ContactPerson $element)
Parse and process a ContactPerson element.
static parseAttributeConsumerService(\SAML2\XML\md\AttributeConsumingService $element, &$sp)
This function parses AttributeConsumerService elements.
static parseFile($file)
This function parses a file which contains XML encoded metadata.
Definition: SAMLParser.php:208
validateFingerprint($fingerprint)
This function checks if this EntityDescriptor was signed with a certificate with the given fingerprin...
processOrganization(\SAML2\XML\md\Organization $element)
Parse and process a Organization element.
static parseElement($entityElement)
This function parses a \SAML2\XML\md\EntityDescriptor object which represents a EntityDescriptor elem...
Definition: SAMLParser.php:267
getMetadata1xIdP()
This function returns the metadata for SAML 1.x IdPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:594
$key
Definition: croninfo.php:18
for( $i=6;$i< 13;$i++) for($i=1; $i< 13; $i++) $d
Definition: date.php:296
$i
Definition: disco.tpl.php:19
$r
Definition: example_031.php:79
$format
Definition: metadata.php:141
$expire
Definition: saml2-acs.php:140
$certificates
Definition: metarefresh.php:39
Attribute-related utility methods.
$ret
Definition: parser.php:6
$idp
Definition: prp.php:13
$values
$data
Definition: bench.php:6