diff --git a/modules/adfs/lib/IdP/ADFS.php b/modules/adfs/lib/IdP/ADFS.php index 691480b7bf5e20a8cc8568688b1be3b4218c315d..0989d11735d49477bc1afa94b24844b21fa620d1 100644 --- a/modules/adfs/lib/IdP/ADFS.php +++ b/modules/adfs/lib/IdP/ADFS.php @@ -4,6 +4,8 @@ namespace SimpleSAML\Module\adfs\IdP; use RobRichards\XMLSecLibs\XMLSecurityDSig; use RobRichards\XMLSecLibs\XMLSecurityKey; +use SimpleSAML\Utils\Config\Metadata; +use SimpleSAML\Utils\Crypto; class ADFS { @@ -143,6 +145,132 @@ MSG; $t->show(); } + + /** + * Get the metadata of a given hosted ADFS IdP. + * + * @param string $entityid The entity ID of the hosted ADFS IdP whose metadata we want to fetch. + * + * @return array + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\MetadataNotFound + */ + public static function getHostedMetadata($entityid) + { + $handler = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler(); + $config = $handler->getMetaDataConfig($entityid, 'adfs-idp-hosted'); + + $endpoint = \SimpleSAML\Module::getModuleURL('adfs/idp/prp.php'); + $metadata = [ + 'metadata-set' => 'adfs-idp-hosted', + 'entityid' => $entityid, + 'SingleSignOnService' => [ + [ + 'Binding' => \SAML2\Constants::BINDING_HTTP_REDIRECT, + 'Location' => $endpoint, + ] + ], + 'SingleLogoutService' => [ + 'Binding' => \SAML2\Constants::BINDING_HTTP_REDIRECT, + 'Location' => $endpoint, + ], + 'NameIDFormat' => $config->getString('NameIDFormat', \SAML2\Constants::NAMEID_TRANSIENT), + 'contacts' => [], + ]; + + // add certificates + $keys = []; + $certInfo = Crypto::loadPublicKey($config, false, 'new_'); + $hasNewCert = false; + if ($certInfo !== null) { + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => true, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => 'new_', + ]; + $hasNewCert = true; + } + + $certInfo = Crypto::loadPublicKey($config, true); + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => $hasNewCert === false, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => '', + ]; + + if ($config->hasValue('https.certificate')) { + $httpsCert = Crypto::loadPublicKey($config, true, 'https.'); + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => false, + 'X509Certificate' => $httpsCert['certData'], + 'prefix' => 'https.' + ]; + } + $metadata['keys'] = $keys; + + // add organization information + if ($config->hasValue('OrganizationName')) { + $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName'); + $metadata['OrganizationDisplayName'] = $config->getLocalizedString( + 'OrganizationDisplayName', + $metadata['OrganizationName'] + ); + + if (!$config->hasValue('OrganizationURL')) { + throw new \SimpleSAMl\Error\Exception('If OrganizationName is set, OrganizationURL must also be set.'); + } + $metadata['OrganizationURL'] = $config->getLocalizedString('OrganizationURL'); + } + + // add scope + if ($config->hasValue('scope')) { + $metadata['scope'] = $config->getArray('scope'); + } + + // add extensions + if ($config->hasValue('EntityAttributes')) { + $metadata['EntityAttributes'] = $config->getArray('EntityAttributes'); + + // check for entity categories + if (Metadata::isHiddenFromDiscovery($metadata)) { + $metadata['hide.from.discovery'] = true; + } + } + + if ($config->hasValue('UIInfo')) { + $metadata['UIInfo'] = $config->getArray('UIInfo'); + } + + if ($config->hasValue('DiscoHints')) { + $metadata['DiscoHints'] = $config->getArray('DiscoHints'); + } + + if ($config->hasValue('RegistrationInfo')) { + $metadata['RegistrationInfo'] = $config->getArray('RegistrationInfo'); + } + + // add contact information + $globalConfig = \SimpleSAML\Configuration::getInstance(); + $email = $globalConfig->getString('technicalcontact_email', false); + if ($email && $email !== 'na@example.org') { + $contact = [ + 'emailAddress' => $email, + 'name' => $globalConfig->getString('technicalcontact_name', null), + 'contactType' => 'technical', + ]; + $metadata['contacts'][] = Metadata::getContact($contact); + } + + return $metadata; + } + + public static function sendResponse(array $state) { $spMetadata = $state["SPMetadata"]; diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php index b2a0f5824d28686bff12dab94105aee082c6747a..9187ed846df8b77813ac994018aa381b0bff957f 100644 --- a/modules/saml/lib/Auth/Source/SP.php +++ b/modules/saml/lib/Auth/Source/SP.php @@ -42,6 +42,14 @@ class SP extends Source */ private $disable_scoping; + /** + * A list of supported protocols. + * + * @var array + */ + private $protocols = []; + + /** * Constructor for SAML SP authentication source. * @@ -98,16 +106,161 @@ class SP extends Source return $this->entityId; } + /** - * Retrieve the metadata of this SP. + * Retrieve the metadata array of this SP, as a remote IdP would see it. * - * @return \SimpleSAML\Configuration The metadata of this SP. + * @return array The metadata array for its use by a remote IdP. */ - public function getMetadata() + public function getHostedMetadata() { - return $this->metadata; + $entityid = $this->getEntityId(); + $metadata = [ + 'entityid' => $entityid, + 'metadata-set' => 'smal20-sp-remote', + 'SingleLogoutService' => $this->getSLOEndpoints(), + 'AssertionConsumerService' => $this->getACSEndpoints(), + ]; + + // add NameIDPolicy + if ($this->metadata->hasValue('NameIDValue')) { + $format = $this->metadata->getValue('NameIDPolicy'); + if (is_array($format)) { + $metadata['NameIDFormat'] = \SimpleSAML\Configuration::loadFromArray($format)->getString( + 'Format', + \SAML2\Constants::NAMEID_TRANSIENT + ); + } elseif (is_string($format)) { + $metadata['NameIDFormat'] = $format; + } + } + + // add attributes + $name = $this->metadata->getLocalizedString('name', null); + $attributes = $this->metadata->getArray('attributes', []); + if ($name !== null) { + if (!empty($attributes)) { + $metadata['name'] = $name; + $metadata['attributes'] = $attributes; + if ($this->metadata->hasValue('attributes.required')) { + $metadata['attributes.required'] = $this->metadata->getArray('attributes.required'); + } + if ($this->metadata->hasValue('description')) { + $metadata['description'] = $this->metadata->getArray('description'); + } + if ($this->metadata->hasValue('attributes.NameFormat')) { + $metadata['attributes.NameFormat'] = $this->metadata->getString('attributes.NameFormat'); + } + if ($this->metadata->hasValue('attributes.index')) { + $metadata['attributes.index'] = $this->metadata->getInteger('attributes.index'); + } + if ($this->metadata->hasValue('attributes.isDefault')) { + $metadata['attributes.isDefault'] = $this->metadata->getBoolean('attributes.isDefault'); + } + } + } + + // add organization info + $org = $this->metadata->getLocalizedString('OrganizationName', null); + if ($org !== null) { + $metadata['OrganizationName'] = $org; + $metadata['OrganizationDisplayName'] = $this->metadata->getLocalizedString('OrganizationDisplayName', $org); + $metadata['OrganizationURL'] = $this->metadata->getLocalizedString('OrganizationURL', null); + if ($metadata['OrganizationURL'] === null) { + throw new \SimpleSAML\Error\Exception( + 'If OrganizationName is set, OrganizationURL must also be set.' + ); + } + } + + // add contacts + $contacts = $this->metadata->getArray('contact', []); + foreach ($contacts as $contact) { + $metadata['contacts'][] = \SimpleSAML\Utils\Config\Metadata::getContact($contact); + } + + // add technical contact + $globalConfig = \SimpleSAML\Configuration::getInstance(); + $email = $globalConfig->getString('technicalcontact_email', 'na@example.org'); + if ($email && $email !== 'na@example.org') { + $contact = [ + 'emailAddress' => $email, + 'name' => $globalConfig->getString('technicalcontact_name', null), + 'contactType' => 'technical', + ]; + $metadata['contacts'][] = \SimpleSAML\Utils\Config\Metadata::getContact($contact); + } + + // add certificate(s) + $certInfo = \SimpleSAML\Utils\Crypto::loadPublicKey($this->metadata, false, 'new_'); + $hasNewCert = false; + if ($certInfo !== null && array_key_exists('certData', $certInfo)) { + $hasNewCert = true; + $metadata['keys'][] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => true, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => 'new_', + 'url' => \SimpleSAML\Module::getModuleURL( + 'admin/cert', + [ + 'sp' => $this->getAuthId(), + 'prefix' => 'new_' + ] + ), + 'name' => 'sp', + ]; + } + + $certInfo = \SimpleSAML\Utils\Crypto::loadPublicKey($this->metadata); + if ($certInfo !== null && array_key_exists('certData', $certInfo)) { + $metadata['keys'][] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => $hasNewCert ? false : true, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => '', + 'url' => \SimpleSAML\Module::getModuleURL( + 'admin/cert', + [ + 'sp' => $this->getAuthId(), + 'prefix' => '' + ] + ), + 'name' => 'sp', + ]; + } + + // add EntityAttributes extension + if ($this->metadata->hasValue('EntityAttributes')) { + $metadata['EntityAttributes'] = $this->metadata->getArray('EntityAttributes'); + } + + // add UIInfo extension + if ($this->metadata->hasValue('UIInfo')) { + $metadata['UIInfo'] = $this->metadata->getArray('UIInfo'); + } + + // add RegistrationInfo extension + if ($this->metadata->hasValue('RegistrationInfo')) { + $metadata['RegistrationInfo'] = $this->metadata->getArray('RegistrationInfo'); + } + + // add signature options + if ($this->metadata->hasValue('WantAssertiosnsSigned')) { + $metadata['saml20.sign.assertion'] = $this->metadata->getBoolean('WantAssertionsSigned'); + } + if ($this->metadata->hasValue('redirect.sign')) { + $metadata['redirect.validate'] = $this->metadata->getBoolean('redirect.sign'); + } elseif ($this->metadata->hasValue('sign.authnrequest')) { + $metadata['validate.authnrequest'] = $this->metadata->getBoolean('sign.authnrequest'); + } + + return $metadata; } + /** * Retrieve the metadata of an IdP. * @@ -146,6 +299,142 @@ class SP extends Source var_export($entityId, true)); } + + /** + * Retrieve the metadata of this SP. + * + * @return \SimpleSAML\Configuration The metadata of this SP. + */ + public function getMetadata() + { + return $this->metadata; + } + + + /** + * Get a list with the protocols supported by this SP. + * + * @return array + */ + public function getSupportedProtocols() + { + return $this->protocols; + } + + + /** + * Get the AssertionConsumerService endpoints for a given local SP. + * + * @return array + * @throws \Exception + */ + private function getACSEndpoints() + { + $endpoints = []; + $default = [ + \SAML2\Constants::BINDING_HTTP_POST, + 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post', + \SAML2\Constants::BINDING_HTTP_ARTIFACT, + 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01', + ]; + if ($this->metadata->getString('ProtocolBinding', '') === \SAML2\Constants::BINDING_HOK_SSO) { + $default[] = \SAML2\Constants::BINDING_HOK_SSO; + } + + $bindings = $this->metadata->getArray('acs.Bindings', $default); + $index = 0; + foreach ($bindings as $service) { + switch ($service) { + case \SAML2\Constants::BINDING_HTTP_POST: + $acs = [ + 'Binding' => \SAML2\Constants::BINDING_HTTP_POST, + 'Location' => \SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/'.$this->getAuthId()), + ]; + if (!in_array(\SAML2\Constants::NS_SAMLP, $this->protocols, true)) { + $this->protocols[] = \SAML2\Constants::NS_SAMLP; + } + break; + case 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post': + $acs = [ + 'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post', + 'Location' => \SimpleSAML\Module::getModuleURL('saml/sp/saml1-acs.php/'.$this->getAuthId()), + ]; + if (!in_array('urn:oasis:names:tc:SAML:1.0:profiles:browser-post', $this->protocols, true)) { + $this->protocols[] = 'urn:oasis:names:tc:SAML:1.1:protocol'; + } + break; + case \SAML2\Constants::BINDING_HTTP_ARTIFACT: + $acs = [ + 'Binding' => \SAML2\Constants::BINDING_HTTP_ARTIFACT, + 'Location' => \SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/'.$this->getAuthId()), + ]; + if (!in_array(\SAML2\Constants::NS_SAMLP, $this->protocols, true)) { + $this->protocols[] = \SAML2\Constants::NS_SAMLP; + } + break; + case 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01': + $acs = [ + 'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01', + 'Location' => \SimpleSAML\Module::getModuleURL( + 'saml/sp/saml1-acs.php/'.$this->getAuthId().'/artifact' + ), + ]; + if (!in_array('urn:oasis:names:tc:SAML:1.1:protocol', $this->protocols, true)) { + $this->protocols[] = 'urn:oasis:names:tc:SAML:1.1:protocol'; + } + break; + case \SAML2\Constants::BINDING_HOK_SSO: + $acs = [ + 'Binding' => \SAML2\Constants::BINDING_HOK_SSO, + 'Location' => \SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/'.$this->getAuthId()), + 'hoksso:ProtocolBinding' => \SAML2\Constants::BINDING_HTTP_REDIRECT, + ]; + if (!in_array(\SAML2\Constants::NS_SAMLP, $this->protocols, true)) { + $this->protocols[] = \SAML2\Constants::NS_SAMLP; + } + break; + } + $acs['index'] = $index; + $endpoints[] = $acs; + $index++; + } + return $endpoints; + } + + + /** + * Get the SingleLogoutService endpoints available for a given local SP. + * + * @return array + * @throws \SimpleSAML\Error\CriticalConfigurationError + */ + private function getSLOEndpoints() + { + $store = \SimpleSAML\Store::getInstance(); + $bindings = $this->metadata->getArray( + 'SingleLogoutServiceBinding', + [ + \SAML2\Constants::BINDING_HTTP_REDIRECT, + \SAML2\Constants::BINDING_SOAP, + ] + ); + $location = \SimpleSAML\Module::getModuleURL('saml/sp/saml2-logout.php/'.$this->getAuthId()); + + $endpoints = []; + foreach ($bindings as $binding) { + if ($binding == \SAML2\Constants::BINDING_SOAP && !($store instanceof \SimpleSAML\Store\SQL)) { + // we cannot properly support SOAP logout + continue; + } + $endpoints[] = [ + 'Binding' => $binding, + 'Location' => $location, + ]; + } + return $endpoints; + } + + /** * Send a SAML1 SSO request to an IdP. * diff --git a/modules/saml/lib/IdP/SAML1.php b/modules/saml/lib/IdP/SAML1.php index 3f1d734d4331da523cf0d5301d9737004cf8ad14..e158a666b590f08c8b7add6e289ce48a4cd2b324 100644 --- a/modules/saml/lib/IdP/SAML1.php +++ b/modules/saml/lib/IdP/SAML1.php @@ -3,6 +3,8 @@ namespace SimpleSAML\Module\saml\IdP; use SimpleSAML\Bindings\Shib13\HTTPPost; +use SimpleSAML\Utils\Config\Metadata; +use SimpleSAML\Utils\Crypto; use SimpleSAML\Utils\HTTP; /** @@ -13,6 +15,112 @@ use SimpleSAML\Utils\HTTP; class SAML1 { + + /** + * Retrieve the metadata of a hosted SAML 1.1 IdP. + * + * @param string $entityid The entity ID of the hosted SAML 1.1 IdP whose metadata we want. + * + * @return array + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\MetadataNotFound + * @throws \SimpleSAML_Error_Exception + */ + public static function getHostedMetadata($entityid) + { + $handler = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler(); + $config = $handler->getMetaDataConfig($entityid, 'shib13-idp-hosted'); + + $metadata = [ + 'metadata-set' => 'shib13-idp-hosted', + 'entityid' => $entityid, + 'SignleSignOnService' => $handler->getGenerated('SingleSignOnService', 'shib13-idp-hosted'), + 'NameIDFormat' => $config->getArrayizeString('NameIDFormat', 'urn:mace:shibboleth:1.0:nameIdentifier'), + 'contacts' => [], + ]; + + // add certificates + $keys = []; + $certInfo = Crypto::loadPublicKey($config, false, 'new_'); + $hasNewCert = false; + if ($certInfo !== null) { + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => true, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => 'new_', + ]; + $hasNewCert = true; + } + + $certInfo = Crypto::loadPublicKey($config, true); + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => $hasNewCert === false, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => '', + ]; + $metadata['keys'] = $keys; + + // add organization information + if ($config->hasValue('OrganizationName')) { + $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName'); + $metadata['OrganizationDisplayName'] = $config->getLocalizedString( + 'OrganizationDisplayName', + $metadata['OrganizationName'] + ); + + if (!$config->hasValue('OrganizationURL')) { + throw new \SimpleSAMl\Error\Exception('If OrganizationName is set, OrganizationURL must also be set.'); + } + $metadata['OrganizationURL'] = $config->getLocalizedString('OrganizationURL'); + } + + // add scope + if ($config->hasValue('scope')) { + $metadata['scope'] = $config->getArray('scope'); + } + + // add extensions + if ($config->hasValue('EntityAttributes')) { + $metadata['EntityAttributes'] = $config->getArray('EntityAttributes'); + + // check for entity categories + if (Metadata::isHiddenFromDiscovery($metadata)) { + $metadata['hide.from.discovery'] = true; + } + } + + if ($config->hasValue('UIInfo')) { + $metadata['UIInfo'] = $config->getArray('UIInfo'); + } + + if ($config->hasValue('DiscoHints')) { + $metadata['DiscoHints'] = $config->getArray('DiscoHints'); + } + + if ($config->hasValue('RegistrationInfo')) { + $metadata['RegistrationInfo'] = $config->getArray('RegistrationInfo'); + } + + // add contact information + $globalConfig = \SimpleSAML\Configuration::getInstance(); + $email = $globalConfig->getString('technicalcontact_email', false); + if ($email && $email !== 'na@example.org') { + $contact = [ + 'emailAddress' => $email, + 'name' => $globalConfig->getString('technicalcontact_name', null), + 'contactType' => 'technical', + ]; + $metadata['contacts'][] = Metadata::getContact($contact); + } + + return $metadata; + } + + /** * Send a response to the SP. * diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index f65cdaf4f93f69d44214c7d31e5473b8f588d944..f530451e0f861ec653a0a356e9f8de7827233e1b 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -3,9 +3,13 @@ namespace SimpleSAML\Module\saml\IdP; use RobRichards\XMLSecLibs\XMLSecurityKey; +use SAML2\Constants; use SimpleSAML\Configuration; use SimpleSAML\Logger; use SAML2\SOAP; +use SimpleSAML\Utils\Config\Metadata; +use SimpleSAML\Utils\Crypto; +use SimpleSAML\Utils\HTTP; /** * IdP implementation for SAML 2.0 protocol. @@ -697,6 +701,204 @@ class SAML2 } + /** + * Retrieve the metadata of a hosted SAML 2 IdP. + * + * @param string $entityid The entity ID of the hosted SAML 2 IdP whose metadata we want. + * + * @return array + * @throws \SimpleSAML\Error\CriticalConfigurationError + * @throws \SimpleSAML\Error\Exception + * @throws \SimpleSAML\Error\MetadataNotFound + */ + public static function getHostedMetadata($entityid) + { + $handler = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler(); + $config = $handler->getMetaDataConfig($entityid, 'saml20-idp-hosted'); + + // configure endpoints + $ssob = $handler->getGenerated('SingleSignOnServiceBinding', 'saml20-idp-hosted'); + $slob = $handler->getGenerated('SingleLogoutServiceBinding', 'saml20-idp-hosted'); + $ssol = $handler->getGenerated('SingleSignOnService', 'saml20-idp-hosted'); + $slol = $handler->getGenerated('SingleLogoutService', 'saml20-idp-hosted'); + + $sso = []; + if (is_array($ssob)) { + foreach ($ssob as $binding) { + $sso[] = [ + 'Binding' => $binding, + 'Location' => $ssol, + ]; + } + } else { + $sso[] = [ + 'Binding' => $ssob, + 'Location' => $ssol, + ]; + } + + $slo = []; + if (is_array($slob)) { + foreach ($slob as $binding) { + $slo[] = [ + 'Binding' => $binding, + 'Location' => $slol, + ]; + } + } else { + $slo[] = [ + 'Binding' => $slob, + 'Location' => $slol, + ]; + } + + $metadata = [ + 'metadata-set' => 'saml20-idp-hosted', + 'entityid' => $entityid, + 'SingleSignOnService' => $sso, + 'SingleLogoutService' => $slo, + 'NameIDFormat' => $config->getArrayizeString('NameIDFormat', Constants::NAMEID_TRANSIENT), + ]; + + // add certificates + $keys = []; + $certInfo = Crypto::loadPublicKey($config, false, 'new_'); + $hasNewCert = false; + if ($certInfo !== null) { + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => true, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => 'new_', + ]; + $hasNewCert = true; + } + + $certInfo = Crypto::loadPublicKey($config, true); + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => $hasNewCert === false, + 'X509Certificate' => $certInfo['certData'], + 'prefix' => '', + ]; + + if ($config->hasValue('https.certificate')) { + $httpsCert = Crypto::loadPublicKey($config, true, 'https.'); + $keys[] = [ + 'type' => 'X509Certificate', + 'signing' => true, + 'encryption' => false, + 'X509Certificate' => $httpsCert['certData'], + 'prefix' => 'https.' + ]; + } + $metadata['keys'] = $keys; + + // add ArtifactResolutionService endpoint, if enabled + if ($config->getBoolean('saml20.sendartifact', false)) { + $metadata['ArtifactResolutionService'][] = [ + 'index' => 0, + 'Binding' => Constants::BINDING_SOAP, + 'Location' => HTTP::getBaseURL().'saml2/idp/ArtifactResolutionService.php' + ]; + } + + // add Holder of Key, if enabled + if ($config->getBoolean('saml20.hok.assertion', false)) { + array_unshift( + $metadata['SingleSignOnService'], + [ + 'hoksso:ProtocolBinding' => Constants::BINDING_HTTP_REDIRECT, + 'Binding' => Constants::BINDING_HOK_SSO, + 'Location' => HTTP::getBaseURL().'saml2/idp/SSOService.php', + ] + ); + } + + // add ECP profile, if enabled + if ($config->getBoolean('saml20.ecp', false)) { + $metadata['SingleSignOnService'][] = [ + 'index' => 0, + 'Binding' => Constants::BINDING_SOAP, + 'Location' => HTTP::getBaseURL().'saml2/idp/SSOService.php', + ]; + } + + // add organization information + if ($config->hasValue('OrganizationName')) { + $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName'); + $metadata['OrganizationDisplayName'] = $config->getLocalizedString( + 'OrganizationDisplayName', + $metadata['OrganizationName'] + ); + + if (!$config->hasValue('OrganizationURL')) { + throw new \SimpleSAML\Error\Exception('If OrganizationName is set, OrganizationURL must also be set.'); + } + $metadata['OrganizationURL'] = $config->getLocalizedString('OrganizationURL'); + } + + // add scope + if ($config->hasValue('scope')) { + $metadata['scope'] = $config->getArray('scope'); + } + + // add extensions + if ($config->hasValue('EntityAttributes')) { + $metadata['EntityAttributes'] = $config->getArray('EntityAttributes'); + + // check for entity categories + if (Metadata::isHiddenFromDiscovery($metadata)) { + $metadata['hide.from.discovery'] = true; + } + } + + if ($config->hasValue('UIInfo')) { + $metadata['UIInfo'] = $config->getArray('UIInfo'); + } + + if ($config->hasValue('DiscoHints')) { + $metadata['DiscoHints'] = $config->getArray('DiscoHints'); + } + + if ($config->hasValue('RegistrationInfo')) { + $metadata['RegistrationInfo'] = $config->getArray('RegistrationInfo'); + } + + // configure signature options + if ($config->hasValue('validate.authnrequest')) { + $metadata['sign.authnrequest'] = $config->getBoolean('validate.authnrequest'); + } + + if ($config->hasValue('redirect.validate')) { + $metadata['redirect.sign'] = $config->getBoolean('redirect.validate'); + } + + // add contact information + if ($config->hasValue('contacts')) { + $contacts = $config->getArray('contacts'); + foreach ($contacts as $contact) { + $metadata['contacts'][] = Metadata::getContact($contact); + } + } + + $globalConfig = \SimpleSAML\Configuration::getInstance(); + $email = $globalConfig->getString('technicalcontact_email', false); + if ($email && $email !== 'na@example.org') { + $contact = [ + 'emailAddress' => $email, + 'name' => $globalConfig->getString('technicalcontact_name', null), + 'contactType' => 'technical', + ]; + $metadata['contacts'][] = Metadata::getContact($contact); + } + + return $metadata; + } + + /** * Calculate the NameID value that should be used. *