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 
57  private $spDescriptors;
58 
59 
68  private $idpDescriptors;
69 
70 
77 
78 
86  private $organizationName = array();
87 
88 
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)) {
470  $ret['url'] = $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) {
861  $key = self::parseKeyDescriptor($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) {
933  self::parseAttributeConsumerService($attcs[0], $sp);
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) {
1297  $ret[] = self::parseGenericEndpoint($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 }
static parseAttributeConsumerService(\SAML2\XML\md\AttributeConsumingService $element, &$sp)
This function parses AttributeConsumerService elements.
$expire
Definition: saml2-acs.php:140
static parseString($metadata)
This function parses a string which contains XML encoded metadata.
Definition: SAMLParser.php:246
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.
$format
Definition: metadata.php:141
static parseDocument($document)
This function parses a DOMDocument which is assumed to contain a single EntityDescriptor element...
Definition: SAMLParser.php:265
static parseKeyDescriptor(\SAML2\XML\md\KeyDescriptor $kd)
This function parses a KeyDescriptor element.
processAttributeAuthorityDescriptor(\SAML2\XML\md\AttributeAuthorityDescriptor $element, $expireTime)
This function extracts metadata from a AttributeAuthorityDescriptor element.
Definition: SAMLParser.php:983
static processExtensions($element, $parentExtensions=array())
Parse an Extensions element.
static findEntityDescriptor($doc)
This function locates the EntityDescriptor node in a DOMDocument.
static debug($string)
Definition: Logger.php:213
static parseGenericEndpoint(\SAML2\XML\md\EndpointType $element)
This function is a generic endpoint element parser.
processContactPerson(\SAML2\XML\md\ContactPerson $element)
Parse and process a ContactPerson element.
$certificates
Definition: metarefresh.php:39
$metadata['__DYNAMIC:1__']
static parseRoleDescriptorType(\SAML2\XML\md\RoleDescriptor $element, $expireTime)
Parse a RoleDescriptorType element.
Definition: SAMLParser.php:843
processOrganization(\SAML2\XML\md\Organization $element)
Parse and process a Organization element.
getAttributeAuthorities()
Retrieve AttributeAuthorities from the metadata.
Definition: SAMLParser.php:823
__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 parseDescriptorsFile($file)
This function parses a file where the root node is either an EntityDescriptor element or an EntitiesD...
Definition: SAMLParser.php:302
static parseDescriptorsString($string)
This function parses a string with XML data.
Definition: SAMLParser.php:336
Attribute-related utility methods.
if($format !==null) $name
Definition: metadata.php:146
processIDPSSODescriptor(\SAML2\XML\md\IDPSSODescriptor $element, $expireTime)
This function extracts metadata from a IDPSSODescriptor element.
Definition: SAMLParser.php:957
getMetadata1xIdP()
This function returns the metadata for SAML 1.x IdPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:612
$r
Definition: example_031.php:79
getSPDescriptors($protocols)
This function finds SP descriptors which supports one of the given protocols.
static warning($string)
Definition: Logger.php:179
static parseFile($file)
This function parses a file which contains XML encoded metadata.
Definition: SAMLParser.php:224
getMetadata20IdP()
This function returns the metadata for SAML 2.0 IdPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:765
validateFingerprint($fingerprint)
This function checks if this EntityDescriptor was signed with a certificate with the given fingerprin...
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
const NAMEFORMAT_UNSPECIFIED
The interpretation of the attribute name is left to individual implementations.
Definition: Constants.php:141
Create styles array
The data for the language used.
addExtensions(array &$metadata, array $roleDescriptor)
Add data parsed from extensions to metadata.
Definition: SAMLParser.php:487
This is class for parsing of SAML 1.x and SAML 2.0 metadata.
Definition: SAMLParser.php:15
$idp
Definition: prp.php:13
static getCertPath($path)
Resolves a path that may be relative to the cert-directory.
Definition: Config.php:22
$ret
Definition: parser.php:6
$i
Definition: disco.tpl.php:19
static extractEndpoints(array $endpoints)
Extract generic endpoints.
if(!file_exists("$old.txt")) if($old===$new) if(file_exists("$new.txt")) $file
static getExpireTime($element, $maxExpireTime)
Determine how long a given element can be cached.
Definition: SAMLParser.php:430
validateSignature($certificates)
If this EntityDescriptor was signed this function use the public key to check the signature...
getMetadata20SP()
This function returns the metadata for SAML 2.0 SPs in the format SimpleSAMLphp expects.
Definition: SAMLParser.php:668
static parseDescriptorsElement(DOMElement $element=null)
This function parses a DOMElement which represents either an EntityDescriptor element or an EntitiesD...
Definition: SAMLParser.php:359
$key
Definition: croninfo.php:18
processSPSSODescriptor(\SAML2\XML\md\SPSSODescriptor $element, $expireTime)
This function extracts metadata from a SPSSODescriptor element.
Definition: SAMLParser.php:921
static parseElement($entityElement)
This function parses a object which represents a EntityDescriptor element.
Definition: SAMLParser.php:283
getEntityId()
This function returns the entity id of this parsed entity.
Definition: SAMLParser.php:448
for($i=6; $i< 13; $i++) for($i=1; $i< 13; $i++) $d
Definition: date.php:296
static parseSSODescriptor(\SAML2\XML\md\SSODescriptorType $element, $expireTime)
This function extracts metadata from a SSODescriptor element.
Definition: SAMLParser.php:894