ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
SAMLParser.php
Go to the documentation of this file.
1<?php
2
3
16{
17
23 private static $SAML1xProtocols = array(
24 'urn:oasis:names:tc:SAML:1.0:protocol',
25 'urn:oasis:names:tc:SAML:1.1:protocol',
26 );
27
28
34 private static $SAML20Protocols = array(
35 'urn:oasis:names:tc:SAML:2.0:protocol',
36 );
37
38
44 private $entityId;
45
46
58
59
69
70
77
78
86 private $organizationName = array();
87
88
96 private $organizationDisplayName = array();
97
98
105 private $organizationURL = array();
106
107
113 private $contacts = array();
114
115
119 private $scopes;
120
121
126
132
136 private $tags;
137
138
144 private $validators = array();
145
146
153
154
164 private function __construct(
165 \SAML2\XML\md\EntityDescriptor $entityElement,
166 $maxExpireTime,
167 array $validators = array(),
168 array $parentExtensions = null
169 ) {
170 assert('is_null($maxExpireTime) || is_int($maxExpireTime)');
171
172 $this->spDescriptors = array();
173 $this->idpDescriptors = array();
174
175 $e = $entityElement->toXML();
176 $e = $e->ownerDocument->saveXML($e);
177 $this->entityDescriptor = base64_encode($e);
178 $this->entityId = $entityElement->entityID;
179
180 $expireTime = self::getExpireTime($entityElement, $maxExpireTime);
181
182 $this->validators = $validators;
183 $this->validators[] = $entityElement;
184
185 // process Extensions element, if it exists
186 $ext = self::processExtensions($entityElement, $parentExtensions);
187 $this->scopes = $ext['scope'];
188 $this->tags = $ext['tags'];
189 $this->entityAttributes = $ext['EntityAttributes'];
190 $this->registrationInfo = $ext['RegistrationInfo'];
191
192 // look over the RoleDescriptors
193 foreach ($entityElement->RoleDescriptor as $child) {
194
195 if ($child instanceof \SAML2\XML\md\SPSSODescriptor) {
196 $this->processSPSSODescriptor($child, $expireTime);
197 } elseif ($child instanceof \SAML2\XML\md\IDPSSODescriptor) {
198 $this->processIDPSSODescriptor($child, $expireTime);
199 } elseif ($child instanceof \SAML2\XML\md\AttributeAuthorityDescriptor) {
200 $this->processAttributeAuthorityDescriptor($child, $expireTime);
201 }
202 }
203
204 if ($entityElement->Organization) {
205 $this->processOrganization($entityElement->Organization);
206 }
207
208 if (!empty($entityElement->ContactPerson)) {
209 foreach ($entityElement->ContactPerson as $contact) {
210 $this->processContactPerson($contact);
211 }
212 }
213 }
214
215
224 public static function parseFile($file)
225 {
227
228 try {
230 } catch(\Exception $e) {
231 throw new Exception('Failed to read XML from file: '.$file);
232 }
233
234 return self::parseDocument($doc);
235 }
236
237
246 public static function parseString($metadata)
247 {
248 try {
250 } catch(\Exception $e) {
251 throw new Exception('Failed to parse XML string.');
252 }
253
254 return self::parseDocument($doc);
255 }
256
257
265 public static function parseDocument($document)
266 {
267 assert('$document instanceof DOMDocument');
268
269 $entityElement = self::findEntityDescriptor($document);
270
271 return self::parseElement($entityElement);
272 }
273
274
283 public static function parseElement($entityElement)
284 {
285 assert('$entityElement instanceof \SAML2\XML\md\EntityDescriptor');
286
287 return new SimpleSAML_Metadata_SAMLParser($entityElement, null);
288 }
289
290
302 public static function parseDescriptorsFile($file)
303 {
304
305 if ($file === null) {
306 throw new Exception('Cannot open file NULL. File name not specified.');
307 }
308
310
311 try {
313 } catch(\Exception $e) {
314 throw new Exception('Failed to read XML from file: '.$file);
315 }
316
317 if ($doc->documentElement === null) {
318 throw new Exception('Opened file is not an XML document: '.$file);
319 }
320
321 return self::parseDescriptorsElement($doc->documentElement);
322 }
323
324
336 public static function parseDescriptorsString($string)
337 {
338 try {
340 } catch(\Exception $e) {
341 throw new Exception('Failed to parse XML string.');
342 }
343
344 return self::parseDescriptorsElement($doc->documentElement);
345 }
346
347
359 public static function parseDescriptorsElement(DOMElement $element = null)
360 {
361 if ($element === null) {
362 throw new Exception('Document was empty.');
363 }
364
365 if (SimpleSAML\Utils\XML::isDOMNodeOfType($element, 'EntityDescriptor', '@md') === true) {
366 return self::processDescriptorsElement(new \SAML2\XML\md\EntityDescriptor($element));
367 } elseif (SimpleSAML\Utils\XML::isDOMNodeOfType($element, 'EntitiesDescriptor', '@md') === true) {
368 return self::processDescriptorsElement(new \SAML2\XML\md\EntitiesDescriptor($element));
369 } else {
370 throw new Exception('Unexpected root node: ['.$element->namespaceURI.']:'.$element->localName);
371 }
372 }
373
374
387 private static function processDescriptorsElement(
388 $element,
389 $maxExpireTime = null,
390 array $validators = array(),
391 array $parentExtensions = array()
392 ) {
393 assert('is_null($maxExpireTime) || is_int($maxExpireTime)');
394
395 if ($element instanceof \SAML2\XML\md\EntityDescriptor) {
396 $ret = new SimpleSAML_Metadata_SAMLParser($element, $maxExpireTime, $validators, $parentExtensions);
397 $ret = array($ret->getEntityId() => $ret);
399 return $ret;
400 }
401
402 assert('$element instanceof \SAML2\XML\md\EntitiesDescriptor');
403
404 $extensions = self::processExtensions($element, $parentExtensions);
405 $expTime = self::getExpireTime($element, $maxExpireTime);
406
407 $validators[] = $element;
408
409 $ret = array();
410 foreach ($element->children as $child) {
411 $ret += self::processDescriptorsElement($child, $expTime, $validators, $extensions);
412 }
413
414 return $ret;
415 }
416
417
430 private static function getExpireTime($element, $maxExpireTime)
431 {
432 // validUntil may be null
433 $expire = $element->validUntil;
434
435 if ($maxExpireTime !== null && ($expire === null || $maxExpireTime < $expire)) {
436 $expire = $maxExpireTime;
437 }
438
439 return $expire;
440 }
441
442
448 public function getEntityId()
449 {
450 return $this->entityId;
451 }
452
453
454 private function getMetadataCommon()
455 {
456 $ret = array();
457 $ret['entityid'] = $this->entityId;
458 $ret['entityDescriptor'] = $this->entityDescriptor;
459
460 // add organizational metadata
461 if (!empty($this->organizationName)) {
462 $ret['description'] = $this->organizationName;
463 $ret['OrganizationName'] = $this->organizationName;
464 }
465 if (!empty($this->organizationDisplayName)) {
467 $ret['OrganizationDisplayName'] = $this->organizationDisplayName;
468 }
469 if (!empty($this->organizationURL)) {
471 $ret['OrganizationURL'] = $this->organizationURL;
472 }
473
474 //add contact metadata
475 $ret['contacts'] = $this->contacts;
476
477 return $ret;
478 }
479
480
487 private function addExtensions(array &$metadata, array $roleDescriptor)
488 {
489 assert('array_key_exists("scope", $roleDescriptor)');
490 assert('array_key_exists("tags", $roleDescriptor)');
491
492 $scopes = array_merge($this->scopes, array_diff($roleDescriptor['scope'], $this->scopes));
493 if (!empty($scopes)) {
494 $metadata['scope'] = $scopes;
495 }
496
497 $tags = array_merge($this->tags, array_diff($roleDescriptor['tags'], $this->tags));
498 if (!empty($tags)) {
499 $metadata['tags'] = $tags;
500 }
501
502
503 if (!empty($this->registrationInfo)) {
504 $metadata['RegistrationInfo'] = $this->registrationInfo;
505 }
506
507 if (!empty($this->entityAttributes)) {
508 $metadata['EntityAttributes'] = $this->entityAttributes;
509
510 // check for entity categories
511 if (SimpleSAML\Utils\Config\Metadata::isHiddenFromDiscovery($metadata)) {
512 $metadata['hide.from.discovery'] = true;
513 }
514 }
515
516 if (!empty($roleDescriptor['UIInfo'])) {
517 $metadata['UIInfo'] = $roleDescriptor['UIInfo'];
518 }
519
520 if (!empty($roleDescriptor['DiscoHints'])) {
521 $metadata['DiscoHints'] = $roleDescriptor['DiscoHints'];
522 }
523 }
524
525
538 public function getMetadata1xSP()
539 {
540 $ret = $this->getMetadataCommon();
541 $ret['metadata-set'] = 'shib13-sp-remote';
542
543
544 // find SP information which supports one of the SAML 1.x protocols
545 $spd = $this->getSPDescriptors(self::$SAML1xProtocols);
546 if (count($spd) === 0) {
547 return null;
548 }
549
550 // we currently only look at the first SPDescriptor which supports SAML 1.x
551 $spd = $spd[0];
552
553 // add expire time to metadata
554 if (array_key_exists('expire', $spd)) {
555 $ret['expire'] = $spd['expire'];
556 }
557
558 // find the assertion consumer service endpoints
559 $ret['AssertionConsumerService'] = $spd['AssertionConsumerService'];
560
561 // add the list of attributes the SP should receive
562 if (array_key_exists('attributes', $spd)) {
563 $ret['attributes'] = $spd['attributes'];
564 }
565 if (array_key_exists('attributes.required', $spd)) {
566 $ret['attributes.required'] = $spd['attributes.required'];
567 }
568 if (array_key_exists('attributes.NameFormat', $spd)) {
569 $ret['attributes.NameFormat'] = $spd['attributes.NameFormat'];
570 }
571
572 // add name & description
573 if (array_key_exists('name', $spd)) {
574 $ret['name'] = $spd['name'];
575 }
576 if (array_key_exists('description', $spd)) {
577 $ret['description'] = $spd['description'];
578 }
579
580 // add public keys
581 if (!empty($spd['keys'])) {
582 $ret['keys'] = $spd['keys'];
583 }
584
585 // add extensions
586 $this->addExtensions($ret, $spd);
587
588 // prioritize mdui:DisplayName as the name if available
589 if (!empty($ret['UIInfo']['DisplayName'])) {
590 $ret['name'] = $ret['UIInfo']['DisplayName'];
591 }
592
593 return $ret;
594 }
595
596
612 public function getMetadata1xIdP()
613 {
614 $ret = $this->getMetadataCommon();
615 $ret['metadata-set'] = 'shib13-idp-remote';
616
617 // find IdP information which supports the SAML 1.x protocol
618 $idp = $this->getIdPDescriptors(self::$SAML1xProtocols);
619 if (count($idp) === 0) {
620 return null;
621 }
622
623 // we currently only look at the first IDP descriptor which supports SAML 1.x
624 $idp = $idp[0];
625
626 // fdd expire time to metadata
627 if (array_key_exists('expire', $idp)) {
628 $ret['expire'] = $idp['expire'];
629 }
630
631 // find the SSO service endpoints
632 $ret['SingleSignOnService'] = $idp['SingleSignOnService'];
633
634 // find the ArtifactResolutionService endpoint
635 $ret['ArtifactResolutionService'] = $idp['ArtifactResolutionService'];
636
637 // add public keys
638 if (!empty($idp['keys'])) {
639 $ret['keys'] = $idp['keys'];
640 }
641
642 // add extensions
643 $this->addExtensions($ret, $idp);
644
645 // prioritize mdui:DisplayName as the name if available
646 if (!empty($ret['UIInfo']['DisplayName'])) {
647 $ret['name'] = $ret['UIInfo']['DisplayName'];
648 }
649
650 return $ret;
651 }
652
653
668 public function getMetadata20SP()
669 {
670 $ret = $this->getMetadataCommon();
671 $ret['metadata-set'] = 'saml20-sp-remote';
672
673 // find SP information which supports the SAML 2.0 protocol
674 $spd = $this->getSPDescriptors(self::$SAML20Protocols);
675 if (count($spd) === 0) {
676 return null;
677 }
678
679 // we currently only look at the first SPDescriptor which supports SAML 2.0
680 $spd = $spd[0];
681
682 // add expire time to metadata
683 if (array_key_exists('expire', $spd)) {
684 $ret['expire'] = $spd['expire'];
685 }
686
687 // find the assertion consumer service endpoints
688 $ret['AssertionConsumerService'] = $spd['AssertionConsumerService'];
689
690
691 // find the single logout service endpoint
692 $ret['SingleLogoutService'] = $spd['SingleLogoutService'];
693
694
695 // find the NameIDFormat. This may not exist
696 if (count($spd['nameIDFormats']) > 0) {
697 // SimpleSAMLphp currently only supports a single NameIDFormat pr. SP. We use the first one
698 $ret['NameIDFormat'] = $spd['nameIDFormats'][0];
699 }
700
701 // add the list of attributes the SP should receive
702 if (array_key_exists('attributes', $spd)) {
703 $ret['attributes'] = $spd['attributes'];
704 }
705 if (array_key_exists('attributes.required', $spd)) {
706 $ret['attributes.required'] = $spd['attributes.required'];
707 }
708 if (array_key_exists('attributes.NameFormat', $spd)) {
709 $ret['attributes.NameFormat'] = $spd['attributes.NameFormat'];
710 }
711
712 // add name & description
713 if (array_key_exists('name', $spd)) {
714 $ret['name'] = $spd['name'];
715 }
716 if (array_key_exists('description', $spd)) {
717 $ret['description'] = $spd['description'];
718 }
719
720 // add public keys
721 if (!empty($spd['keys'])) {
722 $ret['keys'] = $spd['keys'];
723 }
724
725 // add validate.authnrequest
726 if (array_key_exists('AuthnRequestsSigned', $spd)) {
727 $ret['validate.authnrequest'] = $spd['AuthnRequestsSigned'];
728 }
729
730 // add saml20.sign.assertion
731 if (array_key_exists('WantAssertionsSigned', $spd)) {
732 $ret['saml20.sign.assertion'] = $spd['WantAssertionsSigned'];
733 }
734
735 // add extensions
736 $this->addExtensions($ret, $spd);
737
738 // prioritize mdui:DisplayName as the name if available
739 if (!empty($ret['UIInfo']['DisplayName'])) {
740 $ret['name'] = $ret['UIInfo']['DisplayName'];
741 }
742
743 return $ret;
744 }
745
746
765 public function getMetadata20IdP()
766 {
767 $ret = $this->getMetadataCommon();
768 $ret['metadata-set'] = 'saml20-idp-remote';
769
770 // find IdP information which supports the SAML 2.0 protocol
771 $idp = $this->getIdPDescriptors(self::$SAML20Protocols);
772 if (count($idp) === 0) {
773 return null;
774 }
775
776 // we currently only look at the first IDP descriptor which supports SAML 2.0
777 $idp = $idp[0];
778
779 // add expire time to metadata
780 if (array_key_exists('expire', $idp)) {
781 $ret['expire'] = $idp['expire'];
782 }
783
784 // enable redirect.sign if WantAuthnRequestsSigned is enabled
785 if ($idp['WantAuthnRequestsSigned']) {
786 $ret['sign.authnrequest'] = true;
787 }
788
789 // find the SSO service endpoint
790 $ret['SingleSignOnService'] = $idp['SingleSignOnService'];
791
792 // find the single logout service endpoint
793 $ret['SingleLogoutService'] = $idp['SingleLogoutService'];
794
795 // find the ArtifactResolutionService endpoint
796 $ret['ArtifactResolutionService'] = $idp['ArtifactResolutionService'];
797
798 // add supported nameIDFormats
799 $ret['NameIDFormats'] = $idp['nameIDFormats'];
800
801 // add public keys
802 if (!empty($idp['keys'])) {
803 $ret['keys'] = $idp['keys'];
804 }
805
806 // add extensions
807 $this->addExtensions($ret, $idp);
808
809 // prioritize mdui:DisplayName as the name if available
810 if (!empty($ret['UIInfo']['DisplayName'])) {
811 $ret['name'] = $ret['UIInfo']['DisplayName'];
812 }
813
814 return $ret;
815 }
816
817
823 public function getAttributeAuthorities()
824 {
826 }
827
828
843 private static function parseRoleDescriptorType(\SAML2\XML\md\RoleDescriptor $element, $expireTime)
844 {
845 assert('is_null($expireTime) || is_int($expireTime)');
846
847 $ret = array();
848
849 $expireTime = self::getExpireTime($element, $expireTime);
850
851 if ($expireTime !== null) {
852 // we got an expired timestamp, either from this element or one of the parent elements
853 $ret['expire'] = $expireTime;
854 }
855
856 $ret['protocols'] = $element->protocolSupportEnumeration;
857
858 // process KeyDescriptor elements
859 $ret['keys'] = array();
860 foreach ($element->KeyDescriptor as $kd) {
862 if ($key !== null) {
863 $ret['keys'][] = $key;
864 }
865 }
866
867 $ext = self::processExtensions($element);
868 $ret['scope'] = $ext['scope'];
869 $ret['tags'] = $ext['tags'];
870 $ret['EntityAttributes'] = $ext['EntityAttributes'];
871 $ret['UIInfo'] = $ext['UIInfo'];
872 $ret['DiscoHints'] = $ext['DiscoHints'];
873
874 return $ret;
875 }
876
877
894 private static function parseSSODescriptor(\SAML2\XML\md\SSODescriptorType $element, $expireTime)
895 {
896 assert('is_null($expireTime) || is_int($expireTime)');
897
898 $sd = self::parseRoleDescriptorType($element, $expireTime);
899
900 // find all SingleLogoutService elements
901 $sd['SingleLogoutService'] = self::extractEndpoints($element->SingleLogoutService);
902
903 // find all ArtifactResolutionService elements
904 $sd['ArtifactResolutionService'] = self::extractEndpoints($element->ArtifactResolutionService);
905
906
907 // process NameIDFormat elements
908 $sd['nameIDFormats'] = $element->NameIDFormat;
909
910 return $sd;
911 }
912
913
921 private function processSPSSODescriptor(\SAML2\XML\md\SPSSODescriptor $element, $expireTime)
922 {
923 assert('is_null($expireTime) || is_int($expireTime)');
924
925 $sp = self::parseSSODescriptor($element, $expireTime);
926
927 // find all AssertionConsumerService elements
928 $sp['AssertionConsumerService'] = self::extractEndpoints($element->AssertionConsumerService);
929
930 // find all the attributes and SP name...
931 $attcs = $element->AttributeConsumingService;
932 if (count($attcs) > 0) {
934 }
935
936 // check AuthnRequestsSigned
937 if ($element->AuthnRequestsSigned !== null) {
938 $sp['AuthnRequestsSigned'] = $element->AuthnRequestsSigned;
939 }
940
941 // check WantAssertionsSigned
942 if ($element->WantAssertionsSigned !== null) {
943 $sp['WantAssertionsSigned'] = $element->WantAssertionsSigned;
944 }
945
946 $this->spDescriptors[] = $sp;
947 }
948
949
957 private function processIDPSSODescriptor(\SAML2\XML\md\IDPSSODescriptor $element, $expireTime)
958 {
959 assert('is_null($expireTime) || is_int($expireTime)');
960
961 $idp = self::parseSSODescriptor($element, $expireTime);
962
963 // find all SingleSignOnService elements
964 $idp['SingleSignOnService'] = self::extractEndpoints($element->SingleSignOnService);
965
966 if ($element->WantAuthnRequestsSigned) {
967 $idp['WantAuthnRequestsSigned'] = true;
968 } else {
969 $idp['WantAuthnRequestsSigned'] = false;
970 }
971
972 $this->idpDescriptors[] = $idp;
973 }
974
975
984 \SAML2\XML\md\AttributeAuthorityDescriptor $element,
985 $expireTime
986 ) {
987 assert('is_null($expireTime) || is_int($expireTime)');
988
989 $aad = self::parseRoleDescriptorType($element, $expireTime);
990 $aad['entityid'] = $this->entityId;
991 $aad['metadata-set'] = 'attributeauthority-remote';
992
993 $aad['AttributeService'] = self::extractEndpoints($element->AttributeService);
994 $aad['AssertionIDRequestService'] = self::extractEndpoints($element->AssertionIDRequestService);
995 $aad['NameIDFormat'] = $element->NameIDFormat;
996
997 $this->attributeAuthorityDescriptors[] = $aad;
998 }
999
1000
1010 private static function processExtensions($element, $parentExtensions = array())
1011 {
1012 $ret = array(
1013 'scope' => array(),
1014 'tags' => array(),
1015 'EntityAttributes' => array(),
1016 'RegistrationInfo' => array(),
1017 'UIInfo' => array(),
1018 'DiscoHints' => array(),
1019 );
1020
1021 // Some extensions may get inherited from a parent element
1022 if (($element instanceof \SAML2\XML\md\EntityDescriptor || $element instanceof \SAML2\XML\md\EntitiesDescriptor)
1023 && !empty($parentExtensions['RegistrationInfo'])) {
1024 $ret['RegistrationInfo'] = $parentExtensions['RegistrationInfo'];
1025 }
1026
1027 foreach ($element->Extensions as $e) {
1028
1029 if ($e instanceof \SAML2\XML\shibmd\Scope) {
1030 $ret['scope'][] = $e->scope;
1031 continue;
1032 }
1033
1034 // Entity Attributes are only allowed at entity level extensions and not at RoleDescriptor level
1035 if ($element instanceof \SAML2\XML\md\EntityDescriptor ||
1036 $element instanceof \SAML2\XML\md\EntitiesDescriptor) {
1037
1038
1039 if ($e instanceof \SAML2\XML\mdrpi\RegistrationInfo) {
1040 // Registration Authority cannot be overridden (warn only if override attempts to change the value)
1041 if (isset($ret['RegistrationInfo']['registrationAuthority'])
1042 && $ret['RegistrationInfo']['registrationAuthority'] !== $e->registrationAuthority) {
1043 SimpleSAML\Logger::warning('Invalid attempt to override registrationAuthority \''
1044 . $ret['RegistrationInfo']['registrationAuthority'] . "' with '{$e->registrationAuthority}'");
1045 } else {
1046 $ret['RegistrationInfo']['registrationAuthority'] = $e->registrationAuthority;
1047 }
1048 }
1049 if ($e instanceof \SAML2\XML\mdattr\EntityAttributes && !empty($e->children)) {
1050 foreach ($e->children as $attr) {
1051 // only saml:Attribute are currently supported here. The specifications also allows
1052 // saml:Assertions, which more complex processing
1053 if ($attr instanceof \SAML2\XML\saml\Attribute) {
1054 if (empty($attr->Name) || empty($attr->AttributeValue)) {
1055 continue;
1056 }
1057
1058 // attribute names that is not URI is prefixed as this: '{nameformat}name'
1059 $name = $attr->Name;
1060 if (empty($attr->NameFormat)) {
1061 $name = '{'.\SAML2\Constants::NAMEFORMAT_UNSPECIFIED.'}'.$attr->Name;
1062 } elseif ($attr->NameFormat !== 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri') {
1063 $name = '{'.$attr->NameFormat.'}'.$attr->Name;
1064 }
1065
1066 $values = array();
1067 foreach ($attr->AttributeValue as $attrvalue) {
1068 $values[] = $attrvalue->getString();
1069 }
1070
1071 $ret['EntityAttributes'][$name] = $values;
1072 }
1073 }
1074 }
1075 }
1076
1077 // UIInfo elements are only allowed at RoleDescriptor level extensions
1078 if ($element instanceof \SAML2\XML\md\RoleDescriptor) {
1079 if ($e instanceof \SAML2\XML\mdui\UIInfo) {
1080
1081 $ret['UIInfo']['DisplayName'] = $e->DisplayName;
1082 $ret['UIInfo']['Description'] = $e->Description;
1083 $ret['UIInfo']['InformationURL'] = $e->InformationURL;
1084 $ret['UIInfo']['PrivacyStatementURL'] = $e->PrivacyStatementURL;
1085
1086 foreach ($e->Keywords as $uiItem) {
1087 if (!($uiItem instanceof \SAML2\XML\mdui\Keywords)
1088 || empty($uiItem->Keywords)
1089 || empty($uiItem->lang)
1090 ) {
1091 continue;
1092 }
1093 $ret['UIInfo']['Keywords'][$uiItem->lang] = $uiItem->Keywords;
1094 }
1095 foreach ($e->Logo as $uiItem) {
1096 if (!($uiItem instanceof \SAML2\XML\mdui\Logo)
1097 || empty($uiItem->url)
1098 || empty($uiItem->height)
1099 || empty($uiItem->width)
1100 ) {
1101 continue;
1102 }
1103 $logo = array(
1104 'url' => $uiItem->url,
1105 'height' => $uiItem->height,
1106 'width' => $uiItem->width,
1107 );
1108 if (!empty($uiItem->lang)) {
1109 $logo['lang'] = $uiItem->lang;
1110 }
1111 $ret['UIInfo']['Logo'][] = $logo;
1112 }
1113 }
1114 }
1115
1116 // DiscoHints elements are only allowed at IDPSSODescriptor level extensions
1117 if ($element instanceof \SAML2\XML\md\IDPSSODescriptor) {
1118
1119 if ($e instanceof \SAML2\XML\mdui\DiscoHints) {
1120 $ret['DiscoHints']['IPHint'] = $e->IPHint;
1121 $ret['DiscoHints']['DomainHint'] = $e->DomainHint;
1122 $ret['DiscoHints']['GeolocationHint'] = $e->GeolocationHint;
1123 }
1124 }
1125
1126 if (!($e instanceof \SAML2\XML\Chunk)) {
1127 continue;
1128 }
1129
1130 if ($e->localName === 'Attribute' && $e->namespaceURI === \SAML2\Constants::NS_SAML) {
1131 $attribute = $e->getXML();
1132
1133 $name = $attribute->getAttribute('Name');
1134 $values = array_map(
1135 array('SimpleSAML\Utils\XML', 'getDOMText'),
1136 SimpleSAML\Utils\XML::getDOMChildren($attribute, 'AttributeValue', '@saml2')
1137 );
1138
1139 if ($name === 'tags') {
1140 foreach ($values as $tagname) {
1141 if (!empty($tagname)) {
1142 $ret['tags'][] = $tagname;
1143 }
1144 }
1145 }
1146 }
1147 }
1148 return $ret;
1149 }
1150
1151
1157 private function processOrganization(\SAML2\XML\md\Organization $element)
1158 {
1159 $this->organizationName = $element->OrganizationName;
1160 $this->organizationDisplayName = $element->OrganizationDisplayName;
1161 $this->organizationURL = $element->OrganizationURL;
1162 }
1163
1164
1171 private function processContactPerson(\SAML2\XML\md\ContactPerson $element)
1172 {
1173 $contactPerson = array();
1174 if (!empty($element->contactType)) {
1175 $contactPerson['contactType'] = $element->contactType;
1176 }
1177 if (!empty($element->Company)) {
1178 $contactPerson['company'] = $element->Company;
1179 }
1180 if (!empty($element->GivenName)) {
1181 $contactPerson['givenName'] = $element->GivenName;
1182 }
1183 if (!empty($element->SurName)) {
1184 $contactPerson['surName'] = $element->SurName;
1185 }
1186 if (!empty($element->EmailAddress)) {
1187 $contactPerson['emailAddress'] = $element->EmailAddress;
1188 }
1189 if (!empty($element->TelephoneNumber)) {
1190 $contactPerson['telephoneNumber'] = $element->TelephoneNumber;
1191 }
1192 if (!empty($contactPerson)) {
1193 $this->contacts[] = $contactPerson;
1194 }
1195 }
1196
1197
1204 private static function parseAttributeConsumerService(\SAML2\XML\md\AttributeConsumingService $element, &$sp)
1205 {
1206 assert('is_array($sp)');
1207
1208 $sp['name'] = $element->ServiceName;
1209 $sp['description'] = $element->ServiceDescription;
1210
1211 $format = null;
1212 $sp['attributes'] = array();
1213 $sp['attributes.required'] = array();
1214 foreach ($element->RequestedAttribute as $child) {
1215 $attrname = $child->Name;
1216 $sp['attributes'][] = $attrname;
1217
1218 if ($child->isRequired !== null && $child->isRequired === true) {
1219 $sp['attributes.required'][] = $attrname;
1220 }
1221
1222 if ($child->NameFormat !== null) {
1223 $attrformat = $child->NameFormat;
1224 } else {
1226 }
1227
1228 if ($format === null) {
1229 $format = $attrformat;
1230 } elseif ($format !== $attrformat) {
1232 }
1233 }
1234
1235 if (empty($sp['attributes'])) {
1236 // a really invalid configuration: all AttributeConsumingServices should have one or more attributes
1237 unset($sp['attributes']);
1238 }
1239 if (empty($sp['attributes.required'])) {
1240 unset($sp['attributes.required']);
1241 }
1242
1243 if ($format !== \SAML2\Constants::NAMEFORMAT_UNSPECIFIED && $format !== null) {
1244 $sp['attributes.NameFormat'] = $format;
1245 }
1246 }
1247
1248
1263 private static function parseGenericEndpoint(\SAML2\XML\md\EndpointType $element)
1264 {
1265 $ep = array();
1266
1267 $ep['Binding'] = $element->Binding;
1268 $ep['Location'] = $element->Location;
1269
1270 if ($element->ResponseLocation !== null) {
1271 $ep['ResponseLocation'] = $element->ResponseLocation;
1272 }
1273
1274 if ($element instanceof \SAML2\XML\md\IndexedEndpointType) {
1275 $ep['index'] = $element->index;
1276
1277 if ($element->isDefault !== null) {
1278 $ep['isDefault'] = $element->isDefault;
1279 }
1280 }
1281
1282 return $ep;
1283 }
1284
1285
1293 private static function extractEndpoints(array $endpoints)
1294 {
1295 $ret = array();
1296 foreach ($endpoints as $ep) {
1298 }
1299
1300 return $ret;
1301 }
1302
1303
1318 private static function parseKeyDescriptor(\SAML2\XML\md\KeyDescriptor $kd)
1319 {
1320 $r = array();
1321
1322 if ($kd->use === 'encryption') {
1323 $r['encryption'] = true;
1324 $r['signing'] = false;
1325 } elseif ($kd->use === 'signing') {
1326 $r['encryption'] = false;
1327 $r['signing'] = true;
1328 } else {
1329 $r['encryption'] = true;
1330 $r['signing'] = true;
1331 }
1332
1333 $keyInfo = $kd->KeyInfo;
1334
1335 foreach ($keyInfo->info as $i) {
1336 if ($i instanceof \SAML2\XML\ds\X509Data) {
1337 foreach ($i->data as $d) {
1338 if ($d instanceof \SAML2\XML\ds\X509Certificate) {
1339 $r['type'] = 'X509Certificate';
1340 $r['X509Certificate'] = $d->certificate;
1341 return $r;
1342 }
1343 }
1344 }
1345 }
1346
1347 return null;
1348 }
1349
1350
1358 private function getSPDescriptors($protocols)
1359 {
1360 assert('is_array($protocols)');
1361
1362 $ret = array();
1363
1364 foreach ($this->spDescriptors as $spd) {
1365 $sharedProtocols = array_intersect($protocols, $spd['protocols']);
1366 if (count($sharedProtocols) > 0) {
1367 $ret[] = $spd;
1368 }
1369 }
1370
1371 return $ret;
1372 }
1373
1374
1382 private function getIdPDescriptors($protocols)
1383 {
1384 assert('is_array($protocols)');
1385
1386 $ret = array();
1387
1388 foreach ($this->idpDescriptors as $idpd) {
1389 $sharedProtocols = array_intersect($protocols, $idpd['protocols']);
1390 if (count($sharedProtocols) > 0) {
1391 $ret[] = $idpd;
1392 }
1393 }
1394
1395 return $ret;
1396 }
1397
1398
1410 private static function findEntityDescriptor($doc)
1411 {
1412 assert('$doc instanceof DOMDocument');
1413
1414 // find the EntityDescriptor DOMElement. This should be the first (and only) child of the DOMDocument
1415 $ed = $doc->documentElement;
1416
1417 if ($ed === null) {
1418 throw new Exception('Failed to load SAML metadata from empty XML document.');
1419 }
1420
1421 if (SimpleSAML\Utils\XML::isDOMNodeOfType($ed, 'EntityDescriptor', '@md') === false) {
1422 throw new Exception('Expected first element in the metadata document to be an EntityDescriptor element.');
1423 }
1424
1425 return new \SAML2\XML\md\EntityDescriptor($ed);
1426 }
1427
1428
1439 {
1440 foreach ($certificates as $cert) {
1441 assert('is_string($cert)');
1442 $certFile = \SimpleSAML\Utils\Config::getCertPath($cert);
1443 if (!file_exists($certFile)) {
1444 throw new Exception(
1445 'Could not find certificate file ['.$certFile.'], which is needed to validate signature'
1446 );
1447 }
1448 $certData = file_get_contents($certFile);
1449
1450 foreach ($this->validators as $validator) {
1451 $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public'));
1452 $key->loadKey($certData);
1453 try {
1454 if ($validator->validate($key)) {
1455 return true;
1456 }
1457 } catch (Exception $e) {
1458 // this certificate did not sign this element, skip
1459 }
1460 }
1461 }
1462 SimpleSAML\Logger::debug('Could not validate signature');
1463 return false;
1464 }
1465
1466
1476 public function validateFingerprint($fingerprint)
1477 {
1478 assert('is_string($fingerprint)');
1479
1480 $fingerprint = strtolower(str_replace(":", "", $fingerprint));
1481
1482 $candidates = array();
1483 foreach ($this->validators as $validator) {
1484 foreach ($validator->getValidatingCertificates() as $cert) {
1485
1486 $fp = strtolower(sha1(base64_decode($cert)));
1487 $candidates[] = $fp;
1488 if ($fp === $fingerprint) {
1489 return true;
1490 }
1491 }
1492 }
1493 SimpleSAML\Logger::debug('Fingerprint was ['.$fingerprint.'] not one of ['.join(', ', $candidates).']');
1494 return false;
1495 }
1496}
$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:141
static warning($string)
Definition: Logger.php:179
static debug($string)
Definition: Logger.php:213
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:409
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:538
getIdPDescriptors($protocols)
This function finds IdP descriptors which supports one of the given protocols.
getAttributeAuthorities()
Retrieve AttributeAuthorities from the metadata.
Definition: SAMLParser.php:823
static parseSSODescriptor(\SAML2\XML\md\SSODescriptorType $element, $expireTime)
This function extracts metadata from a SSODescriptor element.
Definition: SAMLParser.php:894
static parseString($metadata)
This function parses a string which contains XML encoded metadata.
Definition: SAMLParser.php:246
static parseDescriptorsElement(DOMElement $element=null)
This function parses a DOMElement which represents either an EntityDescriptor element or an EntitiesD...
Definition: SAMLParser.php:359
static processExtensions($element, $parentExtensions=array())
Parse an Extensions element.
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:668
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:336
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:430
static parseDocument($document)
This function parses a DOMDocument which is assumed to contain a single EntityDescriptor element.
Definition: SAMLParser.php:265
static parseDescriptorsFile($file)
This function parses a file where the root node is either an EntityDescriptor element or an EntitiesD...
Definition: SAMLParser.php:302
getMetadata20IdP()
This function returns the metadata for SAML 2.0 IdPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:765
addExtensions(array &$metadata, array $roleDescriptor)
Add data parsed from extensions to metadata.
Definition: SAMLParser.php:487
processSPSSODescriptor(\SAML2\XML\md\SPSSODescriptor $element, $expireTime)
This function extracts metadata from a SPSSODescriptor element.
Definition: SAMLParser.php:921
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:957
processAttributeAuthorityDescriptor(\SAML2\XML\md\AttributeAuthorityDescriptor $element, $expireTime)
This function extracts metadata from a AttributeAuthorityDescriptor element.
Definition: SAMLParser.php:983
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:843
getEntityId()
This function returns the entity id of this parsed entity.
Definition: SAMLParser.php:448
processContactPerson(\SAML2\XML\md\ContactPerson $element)
Parse and process a ContactPerson element.
__construct(\SAML2\XML\md\EntityDescriptor $entityElement, $maxExpireTime, array $validators=array(), array $parentExtensions=null)
This is the constructor for the SAMLParser class.
Definition: SAMLParser.php:164
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:224
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:283
getMetadata1xIdP()
This function returns the metadata for SAML 1.x IdPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:612
$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
if($format !==null) $name
Definition: metadata.php:146
$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
if(!file_exists("$old.txt")) if( $old===$new) if(file_exists("$new.txt")) $file