1<?php 2 3/** 4 * Metadata lib of OneLogin PHP Toolkit 5 * 6 */ 7 8class OneLogin_Saml2_Metadata 9{ 10 const TIME_VALID = 172800; // 2 days 11 const TIME_CACHED = 604800; // 1 week 12 13 /** 14 * Generates the metadata of the SP based on the settings 15 * 16 * @param array $sp The SP data 17 * @param bool|string $authnsign authnRequestsSigned attribute 18 * @param bool|string $wsign wantAssertionsSigned attribute 19 * @param DateTime|null $validUntil Metadata's valid time 20 * @param int|null $cacheDuration Duration of the cache in seconds 21 * @param array $contacts Contacts info 22 * @param array $organization Organization ingo 23 * @param array $attributes 24 * 25 * @return string SAML Metadata XML 26 */ 27 public static function builder($sp, $authnsign = false, $wsign = false, $validUntil = null, $cacheDuration = null, $contacts = array(), $organization = array(), $attributes = array()) 28 { 29 30 if (!isset($validUntil)) { 31 $validUntil = time() + self::TIME_VALID; 32 } 33 $validUntilTime = gmdate('Y-m-d\TH:i:s\Z', $validUntil); 34 35 if (!isset($cacheDuration)) { 36 $cacheDuration = self::TIME_CACHED; 37 } 38 39 $sls = ''; 40 41 if (isset($sp['singleLogoutService'])) { 42 $slsUrl = htmlspecialchars($sp['singleLogoutService']['url'], ENT_QUOTES); 43 $sls = <<<SLS_TEMPLATE 44 <md:SingleLogoutService Binding="{$sp['singleLogoutService']['binding']}" 45 Location="{$slsUrl}" /> 46 47SLS_TEMPLATE; 48 } 49 50 if ($authnsign) { 51 $strAuthnsign = 'true'; 52 } else { 53 $strAuthnsign = 'false'; 54 } 55 56 if ($wsign) { 57 $strWsign = 'true'; 58 } else { 59 $strWsign = 'false'; 60 } 61 62 $strOrganization = ''; 63 64 if (!empty($organization)) { 65 $organizationInfoNames = array(); 66 $organizationInfoDisplaynames = array(); 67 $organizationInfoUrls = array(); 68 foreach ($organization as $lang => $info) { 69 $organizationInfoNames[] = <<<ORGANIZATION_NAME 70 <md:OrganizationName xml:lang="{$lang}">{$info['name']}</md:OrganizationName> 71ORGANIZATION_NAME; 72 $organizationInfoDisplaynames[] = <<<ORGANIZATION_DISPLAY 73 <md:OrganizationDisplayName xml:lang="{$lang}">{$info['displayname']}</md:OrganizationDisplayName> 74ORGANIZATION_DISPLAY; 75 $organizationInfoUrls[] = <<<ORGANIZATION_URL 76 <md:OrganizationURL xml:lang="{$lang}">{$info['url']}</md:OrganizationURL> 77ORGANIZATION_URL; 78 } 79 $orgData = implode("\n", $organizationInfoNames)."\n".implode("\n", $organizationInfoDisplaynames)."\n".implode("\n", $organizationInfoUrls); 80 $strOrganization = <<<ORGANIZATIONSTR 81 82 <md:Organization> 83{$orgData} 84 </md:Organization> 85ORGANIZATIONSTR; 86 } 87 88 $strContacts = ''; 89 if (!empty($contacts)) { 90 $contactsInfo = array(); 91 foreach ($contacts as $type => $info) { 92 $contactsInfo[] = <<<CONTACT 93 <md:ContactPerson contactType="{$type}"> 94 <md:GivenName>{$info['givenName']}</md:GivenName> 95 <md:EmailAddress>{$info['emailAddress']}</md:EmailAddress> 96 </md:ContactPerson> 97CONTACT; 98 } 99 $strContacts = "\n".implode("\n", $contactsInfo); 100 } 101 102 $strAttributeConsumingService = ''; 103 if (isset($sp['attributeConsumingService'])) { 104 $attrCsDesc = ''; 105 if (isset($sp['attributeConsumingService']['serviceDescription'])) { 106 $attrCsDesc = sprintf( 107 ' <md:ServiceDescription xml:lang="en">%s</md:ServiceDescription>' . PHP_EOL, 108 $sp['attributeConsumingService']['serviceDescription'] 109 ); 110 } 111 if (!isset($sp['attributeConsumingService']['serviceName'])) { 112 $sp['attributeConsumingService']['serviceName'] = 'Service'; 113 } 114 $requestedAttributeData = array(); 115 foreach ($sp['attributeConsumingService']['requestedAttributes'] as $attribute) { 116 $requestedAttributeStr = sprintf(' <md:RequestedAttribute Name="%s"', $attribute['name']); 117 if (isset($attribute['nameFormat'])) { 118 $requestedAttributeStr .= sprintf(' NameFormat="%s"', $attribute['nameFormat']); 119 } 120 if (isset($attribute['friendlyName'])) { 121 $requestedAttributeStr .= sprintf(' FriendlyName="%s"', $attribute['friendlyName']); 122 } 123 if (isset($attribute['isRequired'])) { 124 $requestedAttributeStr .= sprintf(' isRequired="%s"', $attribute['isRequired'] === true ? 'true' : 'false'); 125 } 126 $reqAttrAuxStr = " />"; 127 128 if (isset($attribute['attributeValue']) && !empty($attribute['attributeValue'])) { 129 $reqAttrAuxStr = '>'; 130 if (is_string($attribute['attributeValue'])) { 131 $attribute['attributeValue'] = array($attribute['attributeValue']); 132 } 133 foreach ($attribute['attributeValue'] as $attrValue) { 134 $reqAttrAuxStr .=<<<ATTRIBUTEVALUE 135 136 <saml:AttributeValue xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">{$attrValue}</saml:AttributeValue> 137ATTRIBUTEVALUE; 138 } 139 $reqAttrAuxStr .= "\n </md:RequestedAttribute>"; 140 } 141 142 $requestedAttributeData[] = $requestedAttributeStr . $reqAttrAuxStr; 143 } 144 145 $requestedAttributeStr = implode(PHP_EOL, $requestedAttributeData); 146 $strAttributeConsumingService = <<<METADATA_TEMPLATE 147<md:AttributeConsumingService index="1"> 148 <md:ServiceName xml:lang="en">{$sp['attributeConsumingService']['serviceName']}</md:ServiceName> 149{$attrCsDesc}{$requestedAttributeStr} 150 </md:AttributeConsumingService> 151METADATA_TEMPLATE; 152 } 153 154 $spEntityId = htmlspecialchars($sp['entityId'], ENT_QUOTES); 155 $acsUrl = htmlspecialchars($sp['assertionConsumerService']['url'], ENT_QUOTES); 156 $metadata = <<<METADATA_TEMPLATE 157<?xml version="1.0"?> 158<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" 159 validUntil="{$validUntilTime}" 160 cacheDuration="PT{$cacheDuration}S" 161 entityID="{$spEntityId}"> 162 <md:SPSSODescriptor AuthnRequestsSigned="{$strAuthnsign}" WantAssertionsSigned="{$strWsign}" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> 163{$sls} <md:NameIDFormat>{$sp['NameIDFormat']}</md:NameIDFormat> 164 <md:AssertionConsumerService Binding="{$sp['assertionConsumerService']['binding']}" 165 Location="{$acsUrl}" 166 index="1" /> 167 {$strAttributeConsumingService} 168 </md:SPSSODescriptor>{$strOrganization}{$strContacts} 169</md:EntityDescriptor> 170METADATA_TEMPLATE; 171 return $metadata; 172 } 173 174 /** 175 * Signs the metadata with the key/cert provided 176 * 177 * @param string $metadata SAML Metadata XML 178 * @param string $key x509 key 179 * @param string $cert x509 cert 180 * @param string $signAlgorithm Signature algorithm method 181 * @param string $digestAlgorithm Digest algorithm method 182 * 183 * @return string Signed Metadata 184 * 185 * @throws Exception 186 */ 187 public static function signMetadata($metadata, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA1, $digestAlgorithm = XMLSecurityDSig::SHA1) 188 { 189 return OneLogin_Saml2_Utils::addSign($metadata, $key, $cert, $signAlgorithm, $digestAlgorithm); 190 } 191 192 /** 193 * Adds the x509 descriptors (sign/encriptation) to the metadata 194 * The same cert will be used for sign/encrypt 195 * 196 * @param string $metadata SAML Metadata XML 197 * @param string $cert x509 cert 198 * @param bool $wantsEncrypted Whether to include the KeyDescriptor for encryption 199 * 200 * @return string Metadata with KeyDescriptors 201 * 202 * @throws Exception 203 */ 204 public static function addX509KeyDescriptors($metadata, $cert, $wantsEncrypted = true) 205 { 206 $xml = new DOMDocument(); 207 $xml->preserveWhiteSpace = false; 208 $xml->formatOutput = true; 209 try { 210 $xml = OneLogin_Saml2_Utils::loadXML($xml, $metadata); 211 if (!$xml) { 212 throw new Exception('Error parsing metadata'); 213 } 214 } catch (Exception $e) { 215 throw new Exception('Error parsing metadata. '.$e->getMessage()); 216 } 217 218 $formatedCert = OneLogin_Saml2_Utils::formatCert($cert, false); 219 $x509Certificate = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'X509Certificate', $formatedCert); 220 221 $keyData = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'ds:X509Data'); 222 $keyData->appendChild($x509Certificate); 223 224 $keyInfo = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'ds:KeyInfo'); 225 $keyInfo->appendChild($keyData); 226 227 $keyDescriptor = $xml->createElementNS(OneLogin_Saml2_Constants::NS_MD, "md:KeyDescriptor"); 228 229 $SPSSODescriptor = $xml->getElementsByTagName('SPSSODescriptor')->item(0); 230 $SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild); 231 if ($wantsEncrypted === true) { 232 $SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild); 233 } 234 235 $signing = $xml->getElementsByTagName('KeyDescriptor')->item(0); 236 $signing->setAttribute('use', 'signing'); 237 $signing->appendChild($keyInfo); 238 239 if ($wantsEncrypted === true) { 240 $encryption = $xml->getElementsByTagName('KeyDescriptor')->item(1); 241 $encryption->setAttribute('use', 'encryption'); 242 243 $encryption->appendChild($keyInfo->cloneNode(true)); 244 } 245 246 return $xml->saveXML(); 247 } 248} 249