23 'urn:oasis:names:tc:SAML:1.0:protocol',
24 'urn:oasis:names:tc:SAML:1.1:protocol',
33 'urn:oasis:names:tc:SAML:2.0:protocol',
150 \
SAML2\XML\md\EntityDescriptor $entityElement,
153 array $parentExtensions = array()
155 assert($maxExpireTime === null || is_int($maxExpireTime));
157 $this->spDescriptors = array();
158 $this->idpDescriptors = array();
160 $e = $entityElement->toXML();
161 $e = $e->ownerDocument->saveXML($e);
162 $this->entityDescriptor = base64_encode($e);
163 $this->entityId = $entityElement->entityID;
165 $expireTime = self::getExpireTime($entityElement, $maxExpireTime);
168 $this->validators[] = $entityElement;
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'];
178 foreach ($entityElement->RoleDescriptor as $child) {
179 if ($child instanceof \
SAML2\XML\md\SPSSODescriptor) {
181 } elseif ($child instanceof \
SAML2\XML\md\IDPSSODescriptor) {
183 } elseif ($child instanceof \
SAML2\XML\md\AttributeAuthorityDescriptor) {
188 if ($entityElement->Organization) {
192 if (!empty($entityElement->ContactPerson)) {
193 foreach ($entityElement->ContactPerson as $contact) {
215 throw new Exception(
'Failed to read XML from file: '.$file);
218 return self::parseDocument($doc);
235 throw new Exception(
'Failed to parse XML string.');
238 return self::parseDocument($doc);
253 $entityElement = self::findEntityDescriptor($document);
255 return self::parseElement($entityElement);
269 assert($entityElement instanceof \
SAML2\XML\md\EntityDescriptor);
287 if ($file === null) {
288 throw new Exception(
'Cannot open file NULL. File name not specified.');
296 throw new Exception(
'Failed to read XML from file: '.$file);
299 if ($doc->documentElement === null) {
300 throw new Exception(
'Opened file is not an XML document: '.$file);
303 return self::parseDescriptorsElement($doc->documentElement);
323 throw new Exception(
'Failed to parse XML string.');
326 return self::parseDescriptorsElement($doc->documentElement);
343 if ($element === null) {
344 throw new Exception(
'Document was empty.');
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));
352 throw new Exception(
'Unexpected root node: ['.$element->namespaceURI.
']:'.$element->localName);
369 private static function processDescriptorsElement(
371 $maxExpireTime = null,
373 array $parentExtensions = array()
375 assert($maxExpireTime === null || is_int($maxExpireTime));
377 if ($element instanceof \
SAML2\XML\md\EntityDescriptor) {
384 assert($element instanceof \
SAML2\XML\md\EntitiesDescriptor);
386 $extensions = self::processExtensions($element, $parentExtensions);
387 $expTime = self::getExpireTime($element, $maxExpireTime);
392 foreach ($element->children as $child) {
393 $ret += self::processDescriptorsElement($child, $expTime,
$validators, $extensions);
415 $expire = $element->validUntil;
417 if ($maxExpireTime !== null && (
$expire === null || $maxExpireTime <
$expire)) {
443 if (!empty($this->organizationName)) {
447 if (!empty($this->organizationDisplayName)) {
451 if (!empty($this->organizationURL)) {
471 assert(array_key_exists(
'scope', $roleDescriptor));
472 assert(array_key_exists(
'tags', $roleDescriptor));
474 $scopes = array_merge($this->scopes, array_diff($roleDescriptor[
'scope'], $this->scopes));
479 $tags = array_merge($this->tags, array_diff($roleDescriptor[
'tags'], $this->tags));
481 $metadata[
'tags'] =
$tags;
485 if (!empty($this->registrationInfo)) {
489 if (!empty($this->entityAttributes)) {
493 if (
SimpleSAML\Utils\Config\Metadata::isHiddenFromDiscovery($metadata)) {
494 $metadata[
'hide.from.discovery'] =
true;
498 if (!empty($roleDescriptor[
'UIInfo'])) {
499 $metadata[
'UIInfo'] = $roleDescriptor[
'UIInfo'];
502 if (!empty($roleDescriptor[
'DiscoHints'])) {
503 $metadata[
'DiscoHints'] = $roleDescriptor[
'DiscoHints'];
523 $ret[
'metadata-set'] =
'shib13-sp-remote';
528 if (count($spd) === 0) {
536 if (array_key_exists(
'expire', $spd)) {
537 $ret[
'expire'] = $spd[
'expire'];
541 $ret[
'AssertionConsumerService'] = $spd[
'AssertionConsumerService'];
544 if (array_key_exists(
'attributes', $spd)) {
545 $ret[
'attributes'] = $spd[
'attributes'];
547 if (array_key_exists(
'attributes.required', $spd)) {
548 $ret[
'attributes.required'] = $spd[
'attributes.required'];
550 if (array_key_exists(
'attributes.NameFormat', $spd)) {
551 $ret[
'attributes.NameFormat'] = $spd[
'attributes.NameFormat'];
555 if (array_key_exists(
'name', $spd)) {
556 $ret[
'name'] = $spd[
'name'];
558 if (array_key_exists(
'description', $spd)) {
559 $ret[
'description'] = $spd[
'description'];
563 if (!empty($spd[
'keys'])) {
564 $ret[
'keys'] = $spd[
'keys'];
571 if (!empty(
$ret[
'UIInfo'][
'DisplayName'])) {
572 $ret[
'name'] =
$ret[
'UIInfo'][
'DisplayName'];
597 $ret[
'metadata-set'] =
'shib13-idp-remote';
601 if (count(
$idp) === 0) {
609 if (array_key_exists(
'expire',
$idp)) {
614 $ret[
'SingleSignOnService'] =
$idp[
'SingleSignOnService'];
617 $ret[
'ArtifactResolutionService'] =
$idp[
'ArtifactResolutionService'];
620 if (!empty(
$idp[
'keys'])) {
628 if (!empty(
$ret[
'UIInfo'][
'DisplayName'])) {
629 $ret[
'name'] =
$ret[
'UIInfo'][
'DisplayName'];
653 $ret[
'metadata-set'] =
'saml20-sp-remote';
657 if (count($spd) === 0) {
665 if (array_key_exists(
'expire', $spd)) {
666 $ret[
'expire'] = $spd[
'expire'];
670 $ret[
'AssertionConsumerService'] = $spd[
'AssertionConsumerService'];
674 $ret[
'SingleLogoutService'] = $spd[
'SingleLogoutService'];
678 if (count($spd[
'nameIDFormats']) > 0) {
680 $ret[
'NameIDFormat'] = $spd[
'nameIDFormats'][0];
684 if (array_key_exists(
'attributes', $spd)) {
685 $ret[
'attributes'] = $spd[
'attributes'];
687 if (array_key_exists(
'attributes.required', $spd)) {
688 $ret[
'attributes.required'] = $spd[
'attributes.required'];
690 if (array_key_exists(
'attributes.NameFormat', $spd)) {
691 $ret[
'attributes.NameFormat'] = $spd[
'attributes.NameFormat'];
693 if (array_key_exists(
'attributes.index', $spd)) {
694 $ret[
'attributes.index'] = $spd[
'attributes.index'];
696 if (array_key_exists(
'attributes.isDefault', $spd)) {
697 $ret[
'attributes.isDefault'] = $spd[
'attributes.isDefault'];
701 if (array_key_exists(
'name', $spd)) {
702 $ret[
'name'] = $spd[
'name'];
704 if (array_key_exists(
'description', $spd)) {
705 $ret[
'description'] = $spd[
'description'];
709 if (!empty($spd[
'keys'])) {
710 $ret[
'keys'] = $spd[
'keys'];
714 if (array_key_exists(
'AuthnRequestsSigned', $spd)) {
715 $ret[
'validate.authnrequest'] = $spd[
'AuthnRequestsSigned'];
719 if (array_key_exists(
'WantAssertionsSigned', $spd)) {
720 $ret[
'saml20.sign.assertion'] = $spd[
'WantAssertionsSigned'];
727 if (!empty(
$ret[
'UIInfo'][
'DisplayName'])) {
728 $ret[
'name'] =
$ret[
'UIInfo'][
'DisplayName'];
756 $ret[
'metadata-set'] =
'saml20-idp-remote';
760 if (count(
$idp) === 0) {
768 if (array_key_exists(
'expire',
$idp)) {
773 if (
$idp[
'WantAuthnRequestsSigned']) {
774 $ret[
'sign.authnrequest'] =
true;
778 $ret[
'SingleSignOnService'] =
$idp[
'SingleSignOnService'];
781 $ret[
'SingleLogoutService'] =
$idp[
'SingleLogoutService'];
784 $ret[
'ArtifactResolutionService'] =
$idp[
'ArtifactResolutionService'];
787 $ret[
'NameIDFormats'] =
$idp[
'nameIDFormats'];
790 if (!empty(
$idp[
'keys'])) {
798 if (!empty(
$ret[
'UIInfo'][
'DisplayName'])) {
799 $ret[
'name'] =
$ret[
'UIInfo'][
'DisplayName'];
833 assert($expireTime === null || is_int($expireTime));
837 $expireTime = self::getExpireTime($element, $expireTime);
839 if ($expireTime !== null) {
841 $ret[
'expire'] = $expireTime;
844 $ret[
'protocols'] = $element->protocolSupportEnumeration;
847 $ret[
'keys'] = array();
848 foreach ($element->KeyDescriptor as $kd) {
849 $key = self::parseKeyDescriptor($kd);
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'];
884 assert($expireTime === null || is_int($expireTime));
886 $sd = self::parseRoleDescriptorType($element, $expireTime);
889 $sd[
'SingleLogoutService'] = self::extractEndpoints($element->SingleLogoutService);
892 $sd[
'ArtifactResolutionService'] = self::extractEndpoints($element->ArtifactResolutionService);
896 $sd[
'nameIDFormats'] = $element->NameIDFormat;
911 assert($expireTime === null || is_int($expireTime));
913 $sp = self::parseSSODescriptor($element, $expireTime);
916 $sp[
'AssertionConsumerService'] = self::extractEndpoints($element->AssertionConsumerService);
919 $attcs = $element->AttributeConsumingService;
920 if (count($attcs) > 0) {
921 self::parseAttributeConsumerService($attcs[0], $sp);
925 if ($element->AuthnRequestsSigned !== null) {
926 $sp[
'AuthnRequestsSigned'] = $element->AuthnRequestsSigned;
930 if ($element->WantAssertionsSigned !== null) {
931 $sp[
'WantAssertionsSigned'] = $element->WantAssertionsSigned;
934 $this->spDescriptors[] = $sp;
947 assert($expireTime === null || is_int($expireTime));
949 $idp = self::parseSSODescriptor($element, $expireTime);
952 $idp[
'SingleSignOnService'] = self::extractEndpoints($element->SingleSignOnService);
954 if ($element->WantAuthnRequestsSigned) {
955 $idp[
'WantAuthnRequestsSigned'] =
true;
957 $idp[
'WantAuthnRequestsSigned'] =
false;
960 $this->idpDescriptors[] =
$idp;
972 \
SAML2\XML\md\AttributeAuthorityDescriptor $element,
975 assert($expireTime === null || is_int($expireTime));
977 $aad = self::parseRoleDescriptorType($element, $expireTime);
979 $aad[
'metadata-set'] =
'attributeauthority-remote';
981 $aad[
'AttributeService'] = self::extractEndpoints($element->AttributeService);
982 $aad[
'AssertionIDRequestService'] = self::extractEndpoints($element->AssertionIDRequestService);
983 $aad[
'NameIDFormat'] = $element->NameIDFormat;
985 $this->attributeAuthorityDescriptors[] = $aad;
1003 'EntityAttributes' => array(),
1004 'RegistrationInfo' => array(),
1005 'UIInfo' => array(),
1006 'DiscoHints' => array(),
1010 if (($element instanceof \
SAML2\XML\md\EntityDescriptor || $element instanceof \
SAML2\XML\md\EntitiesDescriptor)
1011 && !empty($parentExtensions[
'RegistrationInfo'])) {
1012 $ret[
'RegistrationInfo'] = $parentExtensions[
'RegistrationInfo'];
1015 foreach ($element->Extensions as $e) {
1016 if ($e instanceof \
SAML2\XML\shibmd\Scope) {
1017 $ret[
'scope'][] = $e->scope;
1022 if ($element instanceof \
SAML2\XML\md\EntityDescriptor ||
1023 $element instanceof \
SAML2\XML\md\EntitiesDescriptor) {
1024 if ($e instanceof \
SAML2\XML\mdrpi\RegistrationInfo) {
1026 if (isset(
$ret[
'RegistrationInfo'][
'registrationAuthority'])
1027 &&
$ret[
'RegistrationInfo'][
'registrationAuthority'] !== $e->registrationAuthority) {
1029 .
$ret[
'RegistrationInfo'][
'registrationAuthority'] .
"' with '{$e->registrationAuthority}'");
1031 $ret[
'RegistrationInfo'][
'registrationAuthority'] = $e->registrationAuthority;
1034 if ($e instanceof \
SAML2\XML\mdattr\EntityAttributes && !empty($e->children)) {
1035 foreach ($e->children as $attr) {
1038 if ($attr instanceof \
SAML2\XML\saml\Attribute) {
1039 if (empty($attr->Name) || empty($attr->AttributeValue)) {
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;
1052 foreach ($attr->AttributeValue as $attrvalue) {
1053 $values[] = $attrvalue->getString();
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;
1070 foreach ($e->Keywords as $uiItem) {
1071 if (!($uiItem instanceof \
SAML2\XML\mdui\Keywords)
1072 || empty($uiItem->Keywords)
1073 || empty($uiItem->lang)
1077 $ret[
'UIInfo'][
'Keywords'][$uiItem->lang] = $uiItem->Keywords;
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)
1088 'url' => $uiItem->url,
1089 'height' => $uiItem->height,
1090 'width' => $uiItem->width,
1092 if (!empty($uiItem->lang)) {
1093 $logo[
'lang'] = $uiItem->lang;
1095 $ret[
'UIInfo'][
'Logo'][] = $logo;
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;
1109 if (!($e instanceof \
SAML2\XML\Chunk)) {
1113 if ($e->localName ===
'Attribute' && $e->namespaceURI === \
SAML2\Constants::NS_SAML) {
1114 $attribute = $e->xml;
1116 $name = $attribute->getAttribute(
'Name');
1118 array(
'SimpleSAML\Utils\XML',
'getDOMText'),
1119 SimpleSAML\Utils\XML::getDOMChildren($attribute,
'AttributeValue',
'@saml2')
1122 if (
$name ===
'tags') {
1123 foreach (
$values as $tagname) {
1124 if (!empty($tagname)) {
1125 $ret[
'tags'][] = $tagname;
1142 $this->organizationName = $element->OrganizationName;
1143 $this->organizationDisplayName = $element->OrganizationDisplayName;
1144 $this->organizationURL = $element->OrganizationURL;
1156 $contactPerson = array();
1157 if (!empty($element->contactType)) {
1158 $contactPerson[
'contactType'] = $element->contactType;
1160 if (!empty($element->Company)) {
1161 $contactPerson[
'company'] = $element->Company;
1163 if (!empty($element->GivenName)) {
1164 $contactPerson[
'givenName'] = $element->GivenName;
1166 if (!empty($element->SurName)) {
1167 $contactPerson[
'surName'] = $element->SurName;
1169 if (!empty($element->EmailAddress)) {
1170 $contactPerson[
'emailAddress'] = $element->EmailAddress;
1172 if (!empty($element->TelephoneNumber)) {
1173 $contactPerson[
'telephoneNumber'] = $element->TelephoneNumber;
1175 if (!empty($contactPerson)) {
1176 $this->contacts[] = $contactPerson;
1189 assert(is_array($sp));
1191 $sp[
'name'] = $element->ServiceName;
1192 $sp[
'description'] = $element->ServiceDescription;
1195 $sp[
'attributes'] = array();
1196 $sp[
'attributes.required'] = array();
1197 foreach ($element->RequestedAttribute as $child) {
1198 $attrname = $child->Name;
1199 $sp[
'attributes'][] = $attrname;
1201 if ($child->isRequired !== null && $child->isRequired ===
true) {
1202 $sp[
'attributes.required'][] = $attrname;
1205 if ($child->NameFormat !== null) {
1206 $attrformat = $child->NameFormat;
1213 } elseif (
$format !== $attrformat) {
1218 if (empty($sp[
'attributes'])) {
1220 unset($sp[
'attributes']);
1222 if (empty($sp[
'attributes.required'])) {
1223 unset($sp[
'attributes.required']);
1227 $sp[
'attributes.NameFormat'] =
$format;
1250 $ep[
'Binding'] = $element->Binding;
1251 $ep[
'Location'] = $element->Location;
1253 if ($element->ResponseLocation !== null) {
1254 $ep[
'ResponseLocation'] = $element->ResponseLocation;
1257 if ($element instanceof \
SAML2\XML\md\IndexedEndpointType) {
1258 $ep[
'index'] = $element->index;
1260 if ($element->isDefault !== null) {
1261 $ep[
'isDefault'] = $element->isDefault;
1279 foreach ($endpoints as $ep) {
1280 $ret[] = self::parseGenericEndpoint($ep);
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;
1312 $r[
'encryption'] =
true;
1313 $r[
'signing'] =
true;
1316 $keyInfo = $kd->KeyInfo;
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;
1343 assert(is_array($protocols));
1347 foreach ($this->spDescriptors as $spd) {
1348 $sharedProtocols = array_intersect($protocols, $spd[
'protocols']);
1349 if (count($sharedProtocols) > 0) {
1367 assert(is_array($protocols));
1371 foreach ($this->idpDescriptors as $idpd) {
1372 $sharedProtocols = array_intersect($protocols, $idpd[
'protocols']);
1373 if (count($sharedProtocols) > 0) {
1398 $ed = $doc->documentElement;
1401 throw new Exception(
'Failed to load SAML metadata from empty XML document.');
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.');
1408 return new \SAML2\XML\md\EntityDescriptor($ed);
1424 assert(is_string($cert));
1426 if (!file_exists($certFile)) {
1428 'Could not find certificate file ['.$certFile.
'], which is needed to validate signature' 1431 $certData = file_get_contents($certFile);
1433 foreach ($this->validators as $validator) {
1434 $key =
new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array(
'type' =>
'public'));
1435 $key->loadKey($certData);
1437 if ($validator->validate(
$key)) {
1461 assert(is_string($fingerprint));
1463 $fingerprint = strtolower(str_replace(
":",
"", $fingerprint));
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) {
$metadata['__DYNAMIC:1__']
Attribute-related utility methods.
static fetch($url, $context=array(), $getHeaders=false)
Helper function to retrieve a file or URL with proxy support, also supporting proxy basic authorizati...
const NAMEFORMAT_UNSPECIFIED
The interpretation of the attribute name is left to individual implementations.
static getCertPath($path)
Resolves a path that may be relative to the cert-directory.
for($i=6; $i< 13; $i++) for($i=1; $i< 13; $i++) $d