/**
  * @param array $legacyResponse
  * @return EngineBlock_Saml2_ResponseAnnotationDecorator
  */
 public function fromOldFormat(array $legacyResponse)
 {
     $legacyResponse = EngineBlock_Corto_XmlToArray::registerNamespaces($legacyResponse);
     $xml = EngineBlock_Corto_XmlToArray::array2xml($legacyResponse);
     $document = new DOMDocument();
     $document->loadXML($xml);
     $response = new SAML2_Response($document->firstChild);
     $annotatedResponse = new EngineBlock_Saml2_ResponseAnnotationDecorator($response);
     return $this->addPrivateVars($annotatedResponse, $legacyResponse);
 }
 public function serve($serviceName)
 {
     // Get the configuration for EngineBlock in it's IdP role.
     $engineIdpEntityId = $this->_server->getUrl('idpMetadataService');
     $engineIdpEntity = $this->_server->getRepository()->fetchIdentityProviderByEntityId($engineIdpEntityId);
     $edugainEntities = array();
     $ssoServiceReplacer = new ServiceReplacer($engineIdpEntity, 'SingleSignOnService', ServiceReplacer::REQUIRED);
     $slServiceReplacer = new ServiceReplacer($engineIdpEntity, 'SingleLogoutService', ServiceReplacer::OPTIONAL);
     $remoteEntities = $this->_server->getRepository()->findEntitiesPublishableInEdugain();
     foreach ($remoteEntities as $entity) {
         // Use EngineBlock certificates
         $entity->certificates = $engineIdpEntity->certificates;
         // Ignore the NameIDFormats the IdP supports, any requests made on this endpoint will use EngineBlock
         // NameIDs, so advertise that.
         unset($entity->nameIdFormat);
         $entity->supportedNameIdFormats = $engineIdpEntity->supportedNameIdFormats;
         // For IdP's replace the SingleSignService with the one from EB
         if ($entity instanceof IdentityProvider) {
             // Replace service locations and bindings with those of EB
             $transparentSsoUrl = $this->_server->getUrl('singleSignOnService', $entity->entityId);
             $ssoServiceReplacer->replace($entity, $transparentSsoUrl);
             $transparentSlUrl = $this->_server->getUrl('singleLogoutService');
             $slServiceReplacer->replace($entity, $transparentSlUrl);
         }
         $entity->contactPersons = $engineIdpEntity->contactPersons;
         $entity = $this->_addRequestAttributes($entity);
         $edugainEntities[] = $entity;
     }
     // Map the IdP configuration to a Corto XMLToArray structured document array
     $mapper = new EngineBlock_Corto_Mapper_Metadata_EdugainDocument($this->_server->getNewId(\OpenConext\Component\EngineBlockFixtures\IdFrame::ID_USAGE_SAML2_METADATA), $this->_server->timeStamp($this->_server->getConfig('metadataValidUntilSeconds', 86400)), true);
     $document = $mapper->setEntities($edugainEntities)->map();
     // Sign the document
     $document = $this->_server->sign($document);
     // Convert the document to XML
     $xml = EngineBlock_Corto_XmlToArray::array2xml($document);
     // If debugging is enabled then validate it according to the schema
     if ($this->_server->getConfig('debug', false)) {
         $validator = new EngineBlock_Xml_Validator('http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd');
         $validator->validate($xml);
     }
     // The spec dictates we use a custom mimetype, but debugging is easier with a normal mimetype
     // also no single SP / IdP complains over this.
     //$this->_server->sendHeader('Content-Type', 'application/samlmetadata+xml');
     $this->_server->sendHeader('Content-Type', 'application/xml');
     $this->_server->sendOutput($xml);
 }
 /**
  * Validates xml against a given schema
  *
  * @param string $xml
  * @return void
  * @throws EngineBlock_Exception in case validating itself fails or if xml does not validate
  */
 public function validate($xml)
 {
     if (!ini_get('allow_url_fopen')) {
         throw new EngineBlock_Exception('Failed validating XML, url_fopen is not allowed');
     }
     // Load schema
     $schemaXml = @file_get_contents($this->_schemaLocation);
     if ($schemaXml === false) {
         throw new EngineBlock_Exception('Failed validating XML, schema url could not be opened: "' . $this->_schemaLocation . '"');
     }
     $schemaXml = $this->_absolutizeSchemaLocations($schemaXml, $this->_schemaLocation);
     $dom = new DOMDocument();
     $dom->loadXML($xml);
     if (!@$dom->schemaValidateSource($schemaXml)) {
         $errorInfo = error_get_last();
         $errorMessage = $errorInfo['message'];
         // @todo improve parsing message by creating custom exceptions for which know that structure of messages
         $parsedErrorMessage = preg_replace('/\\{[^}]*\\}/', '', $errorMessage);
         echo '<pre>' . htmlentities(EngineBlock_Corto_XmlToArray::formatXml($xml)) . '</pre>';
         throw new EngineBlock_Exception("Metadata XML doesn't validate against schema at '{$schemaXml}', gives error:: '{$parsedErrorMessage}'");
     }
 }
Пример #4
0
 public function serve($serviceName)
 {
     // Get the configuration for EngineBlock in it's IdP / SP role without the VO.
     $this->_server->setProcessingMode();
     $engineEntityId = $this->_server->getUrl($serviceName);
     $this->_server->unsetProcessingMode();
     $engineEntity = $this->_server->getRepository()->fetchEntityByEntityId($engineEntityId);
     // Override the EntityID and SSO location to optionally append VO id
     $externalEngineEntityId = $this->_server->getUrl($serviceName);
     $engineEntity->entityId = $externalEngineEntityId;
     if ($serviceName === 'idpMetadataService') {
         $ssoServiceReplacer = new ServiceReplacer($engineEntity, 'SingleSignOnService', ServiceReplacer::REQUIRED);
         $ssoLocation = $this->_server->getUrl('singleSignOnService');
         $ssoServiceReplacer->replace($engineEntity, $ssoLocation);
     }
     // Override Single Logout Service Location with generated url
     $slServiceReplacer = new ServiceReplacer($engineEntity, 'SingleLogoutService', ServiceReplacer::OPTIONAL);
     $slLocation = $this->_server->getUrl('singleLogoutService');
     $slServiceReplacer->replace($engineEntity, $slLocation);
     // Map the IdP configuration to a Corto XMLToArray structured document array
     $mapper = new EngineBlock_Corto_Mapper_Metadata_EdugainDocument($this->_server->getNewId(IdFrame::ID_USAGE_SAML2_METADATA), $this->_server->timeStamp($this->_server->getConfig('metadataValidUntilSeconds', 86400)), false);
     $document = $mapper->setEntity($engineEntity)->map();
     // Sign the document
     $document = $this->_server->sign($document);
     // Convert the document to XML
     $xml = EngineBlock_Corto_XmlToArray::array2xml($document);
     // If debugging is enabled then validate it according to the schema
     if ($this->_server->getConfig('debug', false)) {
         $validator = new EngineBlock_Xml_Validator('http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd');
         $validator->validate($xml);
     }
     // The spec dictates we use a custom mimetype, but debugging is easier with a normal mimetype
     // also no single SP / IdP complains over this.
     //$this->_server->sendHeader('Content-Type', 'application/samlmetadata+xml');
     $this->_server->sendHeader('Content-Type', 'application/xml');
     $this->_server->sendOutput($xml);
 }
 /**
  * @dataProvider xmlOutputProvider
  */
 public function testArrayToXml($phpFile, $xmlFile)
 {
     $phpInput = (require $phpFile);
     $expectedXmlOutput = file_get_contents($xmlFile);
     $this->assertEquals($expectedXmlOutput, EngineBlock_Corto_XmlToArray::array2xml($phpInput));
 }
 /**
  * Sign a Corto_XmlToArray array with XML.
  *
  * @param  $element    Element to sign
  * @return array Signed element
  */
 public function sign(array $element)
 {
     $signingKeyPair = $this->getSigningCertificates();
     $signature = array('__t' => 'ds:Signature', '_xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'ds:SignedInfo' => array('__t' => 'ds:SignedInfo', '_xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#', 'ds:CanonicalizationMethod' => array('_Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#'), 'ds:SignatureMethod' => array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'), 'ds:Reference' => array(0 => array('_URI' => '__placeholder__', 'ds:Transforms' => array('ds:Transform' => array(array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'), array('_Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#'))), 'ds:DigestMethod' => array('_Algorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1'), 'ds:DigestValue' => array('__v' => '__placeholder__')))), 'ds:SignatureValue' => array('__v' => '__placeholder__'), 'ds:KeyInfo' => array('ds:X509Data' => array('ds:X509Certificate' => array('__v' => $signingKeyPair->getCertificate()->toCertData()))));
     // Convert the XMl object to actual XML and get a reference to what we're about to sign
     $canonicalXmlDom = new DOMDocument();
     $canonicalXmlDom->loadXML(EngineBlock_Corto_XmlToArray::array2xml($element));
     // Note that the current element may not be the first or last, because we might include comments, so look for
     // the actual XML element
     $xpath = new DOMXPath($canonicalXmlDom);
     $nodes = $xpath->query('/*[@ID="' . $element['_ID'] . '"]');
     if ($nodes->length < 1) {
         throw new EngineBlock_Corto_ProxyServer_Exception("Unable to sign message can't find element with id to sign?", EngineBlock_Corto_ProxyServer_Exception::CODE_NOTICE);
     }
     $canonicalXmlDom = $nodes->item(0);
     // Now do 'exclusive no-comments' XML cannonicalization
     $canonicalXml = $canonicalXmlDom->C14N(true, false);
     // Hash it, encode it in Base64 and include that as the 'Reference'
     $signature['ds:SignedInfo']['ds:Reference'][0]['ds:DigestValue']['__v'] = base64_encode(sha1($canonicalXml, true));
     $signature['ds:SignedInfo']['ds:Reference'][0]['_URI'] = "#" . $element['_ID'];
     // Now we start the actual signing, instead of signing the entire (possibly large) document,
     // we only sign the 'SignedInfo' which includes the 'Reference' hash
     $canonicalXml2Dom = new DOMDocument();
     $canonicalXml2Dom->loadXML(EngineBlock_Corto_XmlToArray::array2xml($signature['ds:SignedInfo']));
     $canonicalXml2 = $canonicalXml2Dom->firstChild->C14N(true, false);
     $signatureValue = null;
     $signatureValue = $signingKeyPair->getPrivateKey()->sign($canonicalXml2);
     $signature['ds:SignatureValue']['__v'] = base64_encode($signatureValue);
     $element['ds:Signature'] = $signature;
     $element[EngineBlock_Corto_XmlToArray::PRIVATE_PFX]['Signed'] = true;
     return $element;
 }
 public function serve($serviceName)
 {
     // Fetch SP Entity Descriptor for the SP Entity ID that is fetched from the request
     $request = EngineBlock_ApplicationSingleton::getInstance()->getHttpRequest();
     $spEntityId = $request->getQueryParameter('sp-entity-id');
     if ($spEntityId) {
         // See if an sp-entity-id was specified for which we need to use sp specific metadata
         $spEntity = $this->_server->getRepository()->fetchServiceProviderByEntityId($spEntityId);
     }
     // Get the configuration for EngineBlock in it's IdP role.
     $engineIdpEntityId = $this->_server->getUrl('idpMetadataService');
     $engineIdentityProvider = $this->_server->getRepository()->fetchIdentityProviderByEntityId($engineIdpEntityId);
     $idpEntities = array();
     // Note that Shibboleth likes to see it's self in the metadata, so if an sp-entity-id was passed along
     // we make sure the first thing is the Service Provider
     if (isset($spEntity)) {
         $idpEntities[] = $spEntity;
     }
     $ssoServiceReplacer = new ServiceReplacer($engineIdentityProvider, 'SingleSignOnService', ServiceReplacer::REQUIRED);
     $slServiceReplacer = new ServiceReplacer($engineIdentityProvider, 'SingleLogoutService', ServiceReplacer::OPTIONAL);
     if (isset($spEntity)) {
         $identityProviders = $this->_server->getRepository()->findIdentityProvidersByEntityId($this->_server->getRepository()->findAllowedIdpEntityIdsForSp($spEntity));
     } else {
         $identityProviders = $this->_server->getRepository()->findIdentityProviders();
     }
     foreach ($identityProviders as $entity) {
         // Don't add ourselves
         if ($entity->entityId === $engineIdentityProvider->entityId) {
             continue;
         }
         if ($entity->hidden) {
             continue;
         }
         // Use EngineBlock certificates
         $entity->certificates = $engineIdentityProvider->certificates;
         // Ignore the NameIDFormats the IdP supports, any requests made on this endpoint will use EngineBlock
         // NameIDs, so advertise that.
         unset($entity->nameIdFormat);
         $entity->supportedNameIdFormats = $engineIdentityProvider->supportedNameIdFormats;
         // Replace service locations and bindings with those of EB
         $transparentSsoUrl = $this->_server->getUrl('singleSignOnService', $entity->entityId);
         $ssoServiceReplacer->replace($entity, $transparentSsoUrl);
         $transparentSlUrl = $this->_server->getUrl('singleLogoutService');
         $slServiceReplacer->replace($entity, $transparentSlUrl);
         $entity->contactPersons = $engineIdentityProvider->contactPersons;
         $idpEntities[] = $entity;
     }
     // Map the IdP configuration to a Corto XMLToArray structured document array
     $mapper = new EngineBlock_Corto_Mapper_Metadata_EdugainDocument($this->_server->getNewId(\OpenConext\Component\EngineBlockFixtures\IdFrame::ID_USAGE_SAML2_METADATA), $this->_server->timeStamp($this->_server->getConfig('metadataValidUntilSeconds', 86400)), false);
     $document = $mapper->setEntities($idpEntities)->map();
     // Sign the document
     $document = $this->_server->sign($document);
     // Convert the document to XML
     $xml = EngineBlock_Corto_XmlToArray::array2xml($document);
     // If debugging is enabled then validate it according to the schema
     if ($this->_server->getConfig('debug', false)) {
         $validator = new EngineBlock_Xml_Validator('http://docs.oasis-open.org/security/saml/v2.0/saml-schema-metadata-2.0.xsd');
         $validator->validate($xml);
     }
     // The spec dictates we use a custom mimetype, but debugging is easier with a normal mimetype
     // also no single SP / IdP complains over this.
     //$this->_server->sendHeader('Content-Type', 'application/samlmetadata+xml');
     $this->_server->sendHeader('Content-Type', 'application/xml');
     $this->_server->sendOutput($xml);
 }
 protected static function _xml2array(&$elements, $level = 1, $namespaceMapping = array())
 {
     $newElement = array();
     while (isset($elements[self::$counter])) {
         $value = $elements[self::$counter];
         self::$counter++;
         if ($value['type'] == 'close') {
             return $newElement;
         } elseif ($value['type'] == 'cdata') {
             continue;
         }
         $hashedAttributes = array();
         $tagName = $value['tag'];
         if (isset($value['attributes']) && ($attributes = $value['attributes'])) {
             foreach ($attributes as $attributeKey => $attributeValue) {
                 unset($attributes[$attributeKey]);
                 $attributeKey = self::_mapNamespacesToSaml($attributeKey);
                 $hashedAttributes[self::ATTRIBUTE_PFX . $attributeKey] = $attributeValue;
             }
         }
         $complete = array();
         $tagName = self::_mapNamespacesToSaml($tagName);
         $complete[self::TAG_NAME_PFX] = $tagName;
         if ($hashedAttributes) {
             $complete = array_merge($complete, $hashedAttributes);
         }
         if (isset($value['value']) && ($attributeValue = trim($value['value']))) {
             $complete[self::VALUE_PFX] = $attributeValue;
         }
         if ($value['type'] == 'open') {
             $cs = self::_xml2array($elements, $level + 1, $namespaceMapping);
             foreach ($cs as $c) {
                 $tagName = $c[self::TAG_NAME_PFX];
                 unset($c[self::TAG_NAME_PFX]);
                 if (!isset(self::$_singulars[$tagName])) {
                     $complete[$tagName][] = $c;
                 } else {
                     $complete[$tagName] = $c;
                     unset($complete[$tagName][self::TAG_NAME_PFX]);
                 }
             }
         }
         $newElement[] = $complete;
     }
     self::$counter = 0;
     return $newElement;
 }