diff --git a/config-templates/config.php b/config-templates/config.php index 1f9a8653294bddac9a1507b09901b87e045e29d4..64c48d950c4e3e473bc72b5b3b346257f2ef5942 100644 --- a/config-templates/config.php +++ b/config-templates/config.php @@ -67,7 +67,8 @@ $config = array ( /* * Some information about the technical persons running this installation. - * The email address will be used as the recipient address for error reports. + * The email address will be used as the recipient address for error reports, and + * also as the technical contact in generated metadata. */ 'technicalcontact_name' => 'Administrator', 'technicalcontact_email' => 'na@example.org', diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php index b66d06b48eb87b35ec70abcf020530487c2a1985..c963f5498e189e1169a6b4c2fc351010c4789e86 100644 --- a/lib/SimpleSAML/Metadata/SAMLBuilder.php +++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php @@ -32,8 +32,8 @@ class SimpleSAML_Metadata_SAMLBuilder { $this->document = new DOMDocument(); $this->entityDescriptor = $this->createElement('EntityDescriptor'); - $this->entityDescriptor->setAttribute('entityID', $entityId); + $this->document->appendChild($this->entityDescriptor); } @@ -48,6 +48,18 @@ class SimpleSAML_Metadata_SAMLBuilder { } + /** + * Retrieve the EntityDescriptor as text. + * + * This function serializes this EntityDescriptor, and returns it as text. + * + * @return string The serialized EntityDescriptor. + */ + public function getEntityDescriptorText() { + return $this->document->saveXML(); + } + + /** * Add metadata set for entity. * @@ -112,6 +124,7 @@ class SimpleSAML_Metadata_SAMLBuilder { if (array_key_exists('AssertionConsumerService', $metadata)) { $t = $this->createElement('AssertionConsumerService'); + $t->setAttribute('index', '0'); $t->setAttribute('Binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'); $t->setAttribute('Location', $metadata['AssertionConsumerService']); $e->appendChild($t); @@ -184,6 +197,7 @@ class SimpleSAML_Metadata_SAMLBuilder { if (array_key_exists('AssertionConsumerService', $metadata)) { $t = $this->createElement('AssertionConsumerService'); + $t->setAttribute('index', '0'); $t->setAttribute('Binding', 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post'); $t->setAttribute('Location', $metadata['AssertionConsumerService']); $e->appendChild($t); @@ -223,6 +237,84 @@ class SimpleSAML_Metadata_SAMLBuilder { } + /** + * Add contact information. + * + * Accepts a contact type, and an array of the following elements (all are optional): + * - emailAddress Email address (as string), or array of email addresses. + * - telephoneNumber Telephone number of contact (as string), or array of telephone numbers. + * - name Full name of contact, either as <GivenName> <SurName>, or as <SurName>, <GivenName>. + * - surName Surname of contact. + * - givenName Givenname of contact. + * - company Company name of contact. + * + * 'name' will only be used if neither givenName nor surName is present. + * + * The following contact types are allowed: + * "technical", "support", "administrative", "billing", "other" + * + * @param string $type The type of contact. + * @param array $details The details about the contact. + */ + public function addContact($type, $details) { + assert('is_string($type)'); + assert('is_array($details)'); + assert('in_array($type, array("technical", "support", "administrative", "billing", "other"), TRUE)'); + + /* Parse name into givenName and surName. */ + if (isset($details['name']) && empty($details['surName']) && empty($details['givenName'])) { + $names = explode(',', $details['name'], 2); + if (count($names) === 2) { + $details['surName'] = trim($names[0]); + $details['givenName'] = trim($names[1]); + } else { + $names = explode(' ', $details['name'], 2); + if (count($names) === 2) { + $details['givenName'] = trim($names[0]); + $details['surName'] = trim($names[1]); + } else { + $details['surName'] = trim($names[0]); + } + } + } + + $e = $this->createElement('ContactPerson'); + $e->setAttribute('contactType', $type); + + if (isset($details['company'])) { + $e->appendChild($this->createTextElement('Company', $details['company'])); + } + if (isset($details['givenName'])) { + $e->appendChild($this->createTextElement('GivenName', $details['givenName'])); + } + if (isset($details['surName'])) { + $e->appendChild($this->createTextElement('SurName', $details['surName'])); + } + + if (isset($details['emailAddress'])) { + $eas = $details['emailAddress']; + if (!is_array($eas)) { + $eas = array($eas); + } + foreach ($eas as $ea) { + $e->appendChild($this->createTextElement('EmailAddress', $ea)); + } + } + + if (isset($details['telephoneNumber'])) { + $tlfNrs = $details['telephoneNumber']; + if (!is_array($tlfNrs)) { + $tlfNrs = array($tlfNrs); + } + foreach ($tlfNrs as $tlfNr) { + $e->appendChild($this->createTextElement('TelephoneNumber', $tlfNr)); + } + } + + $this->entityDescriptor->appendChild($e); + } + + /** * Create DOMElement in metadata namespace. * @@ -238,6 +330,24 @@ class SimpleSAML_Metadata_SAMLBuilder { } + /** + * Create a DOMElement in metadata namespace with a single text node. + * + * @param string $name The name of the DOMElement. + * @param string $text The text contained in the element. + * @return DOMElement The new DOMElement with a text node. + */ + private function createTextElement($name, $text) { + assert('is_string($name)'); + assert('is_string($text)'); + + $node = $this->createElement($name); + $node->appendChild($this->document->createTextNode($text)); + + return $node; + } + + /** * Add certificate. * diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php index 6c7fb1d44fe6ba70ed1944b8dac4695453b27d2b..f4fee907348cba3a12d8272c02192bf0c3981f98 100644 --- a/www/saml2/idp/metadata.php +++ b/www/saml2/idp/metadata.php @@ -38,57 +38,30 @@ try { $urlSLO = $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted', array('logouttype' => $logouttype)); $urlSLOr = $metadata->getGenerated('SingleLogoutServiceResponse', 'saml20-idp-hosted', array('logouttype' => $logouttype)); - - $metaflat = " - '" . htmlspecialchars($idpentityid) . "' => array( - 'name' => 'Type in a name for this entity', - 'description' => 'and a proper description that would help users know when to select this IdP.', - 'SingleSignOnService' => '" . htmlspecialchars($metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted', array())) . "', - 'SingleLogoutService' => '" . htmlspecialchars($urlSLO) . "'," . - (($urlSLO !== $urlSLOr) ? " - 'SingleLogoutServiceResponse' => '" . htmlspecialchars($urlSLOr) . "'," : "") . " - 'certFingerprint' => '" . strtolower(sha1(base64_decode($data))) ."' - ), -"; + $metaArray = array( + 'name' => 'Type in a name for this entity', + 'description' => 'and a proper description that would help users know when to select this IdP.', + 'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted', array()), + 'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted', array('logouttype' => $logouttype)), + 'SingleLogoutServiceResponse' => $metadata->getGenerated('SingleLogoutServiceResponse', 'saml20-idp-hosted', array('logouttype' => $logouttype)), + 'certFingerprint' => strtolower(sha1(base64_decode($data))), + ); + + if ($metaArray['SingleLogoutServiceResponse'] === $metaArray['SingleLogoutService']) { + unset($metaArray['SingleLogoutServiceResponse']); + } + + $metaflat = var_export($idpentityid, TRUE) . ' => ' . var_export($metaArray, TRUE) . ','; - - $metaxml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> - <EntityDescriptor xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="urn:oasis:names:tc:SAML:2.0:metadata" - entityID="' . htmlspecialchars($idpentityid) . '"> - <IDPSSODescriptor - WantAuthnRequestsSigned="false" - protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> - - <KeyDescriptor use="signing"> - <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> - <ds:X509Data> - <ds:X509Certificate>' . htmlspecialchars($data) . '</ds:X509Certificate> - </ds:X509Data> - </ds:KeyInfo> - </KeyDescriptor> - - - - <!-- Logout endpoints --> - <SingleLogoutService - Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - Location="' . htmlspecialchars($urlSLO) . '" - ResponseLocation="' . htmlspecialchars($urlSLOr) . '" - /> - - - <!-- Supported Name Identifier Formats --> - <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat> - - <!-- AuthenticationRequest Consumer endpoint --> - <SingleSignOnService - Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - Location="' . htmlspecialchars($metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted')) . '" - /> - - </IDPSSODescriptor> -</EntityDescriptor>'; + $metaArray['certificate'] = $idpmeta['certificate']; + $metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid); + $metaBuilder->addMetadataIdP20($metaArray); + $metaBuilder->addContact('technical', array( + 'emailAddress' => $config->getValue('technicalcontact_email'), + 'name' => $config->getValue('technicalcontact_name'), + )); + $metaxml = $metaBuilder->getEntityDescriptorText(); /* Sign the metadata if enabled. */ $metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $idpmeta, 'SAML 2 IdP'); diff --git a/www/saml2/sp/metadata.php b/www/saml2/sp/metadata.php index 9499fd5897bfa56dba5d502e4039ff897eb62e30..0287dc3e3fe1a4b365770aec4bf8cfc9d5f5ca66 100644 --- a/www/saml2/sp/metadata.php +++ b/www/saml2/sp/metadata.php @@ -46,37 +46,24 @@ try { if (!$spmeta['assertionConsumerServiceURL']) throw new Exception('The following parameter is not set in your SAML 2.0 SP Hosted metadata: assertionConsumerServiceURL'); if (!$spmeta['SingleLogOutUrl']) throw new Exception('The following parameter is not set in your SAML 2.0 SP Hosted metadata: SingleLogOutUrl'); */ - - $metaflat = " - '" . htmlspecialchars($spentityid) . "' => array( - 'AssertionConsumerService' => '" . htmlspecialchars($metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted')) . "', - 'SingleLogoutService' => '" . htmlspecialchars($metadata->getGenerated('SingleLogoutService', 'saml20-sp-hosted')) . "' - ), -"; - - $metaxml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<EntityDescriptor entityID="' . htmlspecialchars($spentityid) . '" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"> - <SPSSODescriptor - AuthnRequestsSigned="false" - WantAssertionsSigned="false" - protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> + $metaArray = array( + 'AssertionConsumerService' => $metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted'), + 'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-sp-hosted'), + ); - <SingleLogoutService - Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" - Location="' . htmlspecialchars($metadata->getGenerated('SingleLogoutService', 'saml20-sp-hosted')) . '"/> - - <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat> - - <AssertionConsumerService - index="0" - isDefault="true" - Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - Location="' . htmlspecialchars($metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted')) . '" /> - - </SPSSODescriptor> + $metaflat = var_export($spentityid, TRUE) . ' => ' . var_export($metaArray, TRUE) . ','; -</EntityDescriptor>'; + if (array_key_exists('certificate', $spmeta)) { + $metaArray['certificate'] = $spmeta['certificate']; + } + $metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($spentityid); + $metaBuilder->addMetadataSP20($metaArray); + $metaBuilder->addContact('technical', array( + 'emailAddress' => $config->getValue('technicalcontact_email'), + 'name' => $config->getValue('technicalcontact_name'), + )); + $metaxml = $metaBuilder->getEntityDescriptorText(); /* Sign the metadata if enabled. */ $metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $spmeta, 'SAML 2 SP'); diff --git a/www/shib13/idp/metadata.php b/www/shib13/idp/metadata.php index 526c74581e2da8ca0d7b34c6296a9910f93b3ece..d5ecbfcf5b25b412c8ebf3c50ef6e4aba67d2af2 100644 --- a/www/shib13/idp/metadata.php +++ b/www/shib13/idp/metadata.php @@ -34,41 +34,23 @@ try { $data = XMLSecurityDSig::get509XCert($cert, true); - $metaflat = " - '" . htmlspecialchars($idpentityid) . "' => array( - 'name' => 'Type in a name for this entity', - 'description' => 'and a proper description that would help users know when to select this IdP.', - 'SingleSignOnService' => '" . htmlspecialchars($metadata->getGenerated('SingleSignOnService', 'shib13-idp-hosted')) . "', - 'certFingerprint' => '" . strtolower(sha1(base64_decode($data))) ."' - ), -"; + $metaArray = array( + 'name' => 'Type in a name for this entity', + 'description' => 'and a proper description that would help users know when to select this IdP.', + 'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'shib13-idp-hosted'), + 'certFingerprint' => strtolower(sha1(base64_decode($data))), + ); + + $metaflat = var_export($idpentityid, TRUE) . ' => ' . var_export($metaArray, TRUE) . ','; - $metaxml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<EntityDescriptor entityID="' . htmlspecialchars($idpentityid) . '" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"> - - <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0"> - - <KeyDescriptor use="signing"> - <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> - <ds:X509Data> - <ds:X509Certificate>' . htmlspecialchars($data) . '</ds:X509Certificate> - </ds:X509Data> - </ds:KeyInfo> - </KeyDescriptor> - - <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat> - - <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" - Location="' . htmlspecialchars($metadata->getGenerated('SingleSignOnService', 'shib13-idp-hosted')) . '"/> - - </IDPSSODescriptor> - - <ContactPerson contactType="technical"> - <SurName>' . $config->getValue('technicalcontact_name', 'Not entered') . '</SurName> - <EmailAddress>' . $config->getValue('technicalcontact_email', 'Not entered') . '</EmailAddress> - </ContactPerson> - -</EntityDescriptor>'; + $metaArray['certificate'] = $idpmeta['certificate']; + $metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid); + $metaBuilder->addMetadataIdP11($metaArray); + $metaBuilder->addContact('technical', array( + 'emailAddress' => $config->getValue('technicalcontact_email'), + 'name' => $config->getValue('technicalcontact_name'), + )); + $metaxml = $metaBuilder->getEntityDescriptorText(); /* Sign the metadata if enabled. */ $metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $idpmeta, 'Shib 1.3 IdP'); diff --git a/www/shib13/sp/metadata.php b/www/shib13/sp/metadata.php index b6c08f6bff6844797a4bb51d549aa96354ecc34a..23dc81001fd625f70f7089e139a0caded1d782ed 100644 --- a/www/shib13/sp/metadata.php +++ b/www/shib13/sp/metadata.php @@ -27,28 +27,22 @@ try { $spentityid = isset($_GET['spentityid']) ? $_GET['spentityid'] : $metadata->getMetaDataCurrentEntityID('shib13-sp-hosted'); - $metaflat = " - '" . htmlspecialchars($spentityid) . "' => array( - 'AssertionConsumerService' => '" . htmlspecialchars($metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted')) . "' - ), -"; - - $metaxml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<EntityDescriptor entityID="' . htmlspecialchars($spentityid) . '" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"> - <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol"> + $metaArray = array( + 'AssertionConsumerService' => $metadata->getGenerated('AssertionConsumerService', 'shib13-sp-hosted'), + ); - <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat> - - <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post" Location="' . htmlspecialchars($metadata->getGenerated('AssertionConsumerService', 'shib13-sp-hosted')) . '" index="1" isDefault="true" /> - - </SPSSODescriptor> - - <ContactPerson contactType="technical"> - <SurName>' . $config->getValue('technicalcontact_name', 'Not entered') . '</SurName> - <EmailAddress>' . $config->getValue('technicalcontact_email', 'Not entered') . '</EmailAddress> - </ContactPerson> - -</EntityDescriptor>'; + $metaflat = var_export($spentityid, TRUE) . ' => ' . var_export($metaArray, TRUE) . ','; + + if (array_key_exists('certificate', $spmeta)) { + $metaArray['certificate'] = $spmeta['certificate']; + } + $metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($spentityid); + $metaBuilder->addMetadataSP11($metaArray); + $metaBuilder->addContact('technical', array( + 'emailAddress' => $config->getValue('technicalcontact_email'), + 'name' => $config->getValue('technicalcontact_name'), + )); + $metaxml = $metaBuilder->getEntityDescriptorText(); /* Sign the metadata if enabled. */ $metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $spmeta, 'Shib 1.3 SP');