* @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE * @link https://github.com/onelogin/php-saml */ namespace OneLogin\Saml2; use RobRichards\XMLSecLibs\XMLSecurityKey; use RobRichards\XMLSecLibs\XMLSecurityDSig; use DOMDocument; use Exception; /** * Metadata lib of OneLogin PHP Toolkit */ class Metadata { const TIME_VALID = 172800; // 2 days const TIME_CACHED = 604800; // 1 week /** * Generates the metadata of the SP based on the settings * * @param array $sp The SP data * @param bool|string $authnsign authnRequestsSigned attribute * @param bool|string $wsign wantAssertionsSigned attribute * @param int|null $validUntil Metadata's valid time * @param int|null $cacheDuration Duration of the cache in seconds * @param array $contacts Contacts info * @param array $organization Organization ingo * @param array $attributes * * @return string SAML Metadata XML */ public static function builder($sp, $authnsign = false, $wsign = false, $validUntil = null, $cacheDuration = null, $contacts = array(), $organization = array(), $attributes = array()) { if (!isset($validUntil)) { $validUntil = time() + self::TIME_VALID; } $validUntilTime = Utils::parseTime2SAML($validUntil); if (!isset($cacheDuration)) { $cacheDuration = self::TIME_CACHED; } $sls = ''; if (isset($sp['singleLogoutService'])) { $slsUrl = htmlspecialchars($sp['singleLogoutService']['url'], ENT_QUOTES); $sls = << SLS_TEMPLATE; } if ($authnsign) { $strAuthnsign = 'true'; } else { $strAuthnsign = 'false'; } if ($wsign) { $strWsign = 'true'; } else { $strWsign = 'false'; } $strOrganization = ''; if (!empty($organization)) { $organizationInfoNames = array(); $organizationInfoDisplaynames = array(); $organizationInfoUrls = array(); foreach ($organization as $lang => $info) { $organizationInfoNames[] = <<{$info['name']} ORGANIZATION_NAME; $organizationInfoDisplaynames[] = <<{$info['displayname']} ORGANIZATION_DISPLAY; $organizationInfoUrls[] = <<{$info['url']} ORGANIZATION_URL; } $orgData = implode("\n", $organizationInfoNames)."\n".implode("\n", $organizationInfoDisplaynames)."\n".implode("\n", $organizationInfoUrls); $strOrganization = << {$orgData} ORGANIZATIONSTR; } $strContacts = ''; if (!empty($contacts)) { $contactsInfo = array(); foreach ($contacts as $type => $info) { $contactsInfo[] = << {$info['givenName']} {$info['emailAddress']} CONTACT; } $strContacts = "\n".implode("\n", $contactsInfo); } $strAttributeConsumingService = ''; if (isset($sp['attributeConsumingService'])) { $attrCsDesc = ''; if (isset($sp['attributeConsumingService']['serviceDescription'])) { $attrCsDesc = sprintf( ' %s' . PHP_EOL, $sp['attributeConsumingService']['serviceDescription'] ); } if (!isset($sp['attributeConsumingService']['serviceName'])) { $sp['attributeConsumingService']['serviceName'] = 'Service'; } $requestedAttributeData = array(); foreach ($sp['attributeConsumingService']['requestedAttributes'] as $attribute) { $requestedAttributeStr = sprintf(' {$attrValue} ATTRIBUTEVALUE; } $reqAttrAuxStr .= "\n "; } $requestedAttributeData[] = $requestedAttributeStr . $reqAttrAuxStr; } $requestedAttributeStr = implode(PHP_EOL, $requestedAttributeData); $strAttributeConsumingService = << {$sp['attributeConsumingService']['serviceName']} {$attrCsDesc}{$requestedAttributeStr} METADATA_TEMPLATE; } $spEntityId = htmlspecialchars($sp['entityId'], ENT_QUOTES); $acsUrl = htmlspecialchars($sp['assertionConsumerService']['url'], ENT_QUOTES); $metadata = << {$sls} {$sp['NameIDFormat']} {$strAttributeConsumingService} {$strOrganization}{$strContacts} METADATA_TEMPLATE; return $metadata; } /** * Signs the metadata with the key/cert provided * * @param string $metadata SAML Metadata XML * @param string $key x509 key * @param string $cert x509 cert * @param string $signAlgorithm Signature algorithm method * @param string $digestAlgorithm Digest algorithm method * * @return string Signed Metadata * * @throws Exception */ public static function signMetadata($metadata, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $digestAlgorithm = XMLSecurityDSig::SHA256) { return Utils::addSign($metadata, $key, $cert, $signAlgorithm, $digestAlgorithm); } /** * Adds the x509 descriptors (sign/encryption) to the metadata * The same cert will be used for sign/encrypt * * @param string $metadata SAML Metadata XML * @param string $cert x509 cert * @param bool $wantsEncrypted Whether to include the KeyDescriptor for encryption * * @return string Metadata with KeyDescriptors * * @throws Exception */ public static function addX509KeyDescriptors($metadata, $cert, $wantsEncrypted = true) { $xml = new DOMDocument(); $xml->preserveWhiteSpace = false; $xml->formatOutput = true; try { $xml = Utils::loadXML($xml, $metadata); if (!$xml) { throw new Exception('Error parsing metadata'); } } catch (Exception $e) { throw new Exception('Error parsing metadata. '.$e->getMessage()); } $formatedCert = Utils::formatCert($cert, false); $x509Certificate = $xml->createElementNS(Constants::NS_DS, 'X509Certificate', $formatedCert); $keyData = $xml->createElementNS(Constants::NS_DS, 'ds:X509Data'); $keyData->appendChild($x509Certificate); $keyInfo = $xml->createElementNS(Constants::NS_DS, 'ds:KeyInfo'); $keyInfo->appendChild($keyData); $keyDescriptor = $xml->createElementNS(Constants::NS_MD, "md:KeyDescriptor"); $SPSSODescriptor = $xml->getElementsByTagName('SPSSODescriptor')->item(0); $SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild); if ($wantsEncrypted === true) { $SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild); } $signing = $xml->getElementsByTagName('KeyDescriptor')->item(0); $signing->setAttribute('use', 'signing'); $signing->appendChild($keyInfo); if ($wantsEncrypted === true) { $encryption = $xml->getElementsByTagName('KeyDescriptor')->item(1); $encryption->setAttribute('use', 'encryption'); $encryption->appendChild($keyInfo->cloneNode(true)); } return $xml->saveXML(); } }