ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
Assertion.php
Go to the documentation of this file.
1 <?php
2 
3 namespace SAML2;
4 
11 
17 class Assertion implements SignedElement
18 {
24  private $id;
25 
31  private $issueInstant;
32 
41  private $issuer;
42 
50  private $nameId;
51 
60 
69 
75  private $encryptionKey;
76 
82  private $notBefore;
83 
89  private $notOnOrAfter;
90 
101 
108 
116  private $sessionIndex;
117 
123  private $authnInstant;
124 
131 
141 
150 
157 
174  private $attributes;
175 
192 
201  private $nameFormat;
202 
210  private $signatureKey;
211 
217  private $certificates;
218 
224  private $signatureData;
225 
233 
240 
244  protected $wasSignedAtConstruction = false;
245 
250 
257  public function __construct(\DOMElement $xml = null)
258  {
259  $this->id = Utils::getContainer()->generateId();
260  $this->issueInstant = Temporal::getTime();
261  $this->issuer = '';
262  $this->authnInstant = Temporal::getTime();
263  $this->attributes = array();
264  $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
265  $this->certificates = array();
266  $this->AuthenticatingAuthority = array();
267  $this->SubjectConfirmation = array();
268  $this->requiredEncAttributes = false;
269 
270  if ($xml === null) {
271  return;
272  }
273 
274  if (!$xml->hasAttribute('ID')) {
275  throw new \Exception('Missing ID attribute on SAML assertion.');
276  }
277  $this->id = $xml->getAttribute('ID');
278 
279  if ($xml->getAttribute('Version') !== '2.0') {
280  /* Currently a very strict check. */
281  throw new \Exception('Unsupported version: ' . $xml->getAttribute('Version'));
282  }
283 
284  $this->issueInstant = Utils::xsDateTimeToTimestamp($xml->getAttribute('IssueInstant'));
285 
286  $issuer = Utils::xpQuery($xml, './saml_assertion:Issuer');
287  if (empty($issuer)) {
288  throw new \Exception('Missing <saml:Issuer> in assertion.');
289  }
290  $this->issuer = new XML\saml\Issuer($issuer[0]);
291  if ($this->issuer->Format === Constants::NAMEID_ENTITY) {
292  $this->issuer = $this->issuer->value;
293  }
294 
295  $this->parseSubject($xml);
296  $this->parseConditions($xml);
297  $this->parseAuthnStatement($xml);
298  $this->parseAttributes($xml);
299  $this->parseEncryptedAttributes($xml);
300  $this->parseSignature($xml);
301  }
302 
309  private function parseSubject(\DOMElement $xml)
310  {
311  $subject = Utils::xpQuery($xml, './saml_assertion:Subject');
312  if (empty($subject)) {
313  /* No Subject node. */
314 
315  return;
316  } elseif (count($subject) > 1) {
317  throw new \Exception('More than one <saml:Subject> in <saml:Assertion>.');
318  }
319  $subject = $subject[0];
320 
321  $nameId = Utils::xpQuery(
322  $subject,
323  './saml_assertion:NameID | ./saml_assertion:EncryptedID/xenc:EncryptedData'
324  );
325  if (count($nameId) > 1) {
326  throw new \Exception('More than one <saml:NameID> or <saml:EncryptedID> in <saml:Subject>.');
327  } elseif (!empty($nameId)) {
328  $nameId = $nameId[0];
329  if ($nameId->localName === 'EncryptedData') {
330  /* The NameID element is encrypted. */
331  $this->encryptedNameId = $nameId;
332  } else {
333  $this->nameId = new XML\saml\NameID($nameId);
334  }
335  }
336 
337  $subjectConfirmation = Utils::xpQuery($subject, './saml_assertion:SubjectConfirmation');
338  if (empty($subjectConfirmation) && empty($nameId)) {
339  throw new \Exception('Missing <saml:SubjectConfirmation> in <saml:Subject>.');
340  }
341 
342  foreach ($subjectConfirmation as $sc) {
343  $this->SubjectConfirmation[] = new SubjectConfirmation($sc);
344  }
345  }
346 
353  private function parseConditions(\DOMElement $xml)
354  {
355  $conditions = Utils::xpQuery($xml, './saml_assertion:Conditions');
356  if (empty($conditions)) {
357  /* No <saml:Conditions> node. */
358 
359  return;
360  } elseif (count($conditions) > 1) {
361  throw new \Exception('More than one <saml:Conditions> in <saml:Assertion>.');
362  }
363  $conditions = $conditions[0];
364 
365  if ($conditions->hasAttribute('NotBefore')) {
366  $notBefore = Utils::xsDateTimeToTimestamp($conditions->getAttribute('NotBefore'));
367  if ($this->notBefore === null || $this->notBefore < $notBefore) {
368  $this->notBefore = $notBefore;
369  }
370  }
371  if ($conditions->hasAttribute('NotOnOrAfter')) {
372  $notOnOrAfter = Utils::xsDateTimeToTimestamp($conditions->getAttribute('NotOnOrAfter'));
373  if ($this->notOnOrAfter === null || $this->notOnOrAfter > $notOnOrAfter) {
374  $this->notOnOrAfter = $notOnOrAfter;
375  }
376  }
377 
378  for ($node = $conditions->firstChild; $node !== null; $node = $node->nextSibling) {
379  if ($node instanceof \DOMText) {
380  continue;
381  }
382  if ($node->namespaceURI !== Constants::NS_SAML) {
383  throw new \Exception('Unknown namespace of condition: ' . var_export($node->namespaceURI, true));
384  }
385  switch ($node->localName) {
386  case 'AudienceRestriction':
387  $audiences = Utils::extractStrings($node, Constants::NS_SAML, 'Audience');
388  if ($this->validAudiences === null) {
389  /* The first (and probably last) AudienceRestriction element. */
390  $this->validAudiences = $audiences;
391  } else {
392  /*
393  * The set of AudienceRestriction are ANDed together, so we need
394  * the subset that are present in all of them.
395  */
396  $this->validAudiences = array_intersect($this->validAudiences, $audiences);
397  }
398  break;
399  case 'OneTimeUse':
400  /* Currently ignored. */
401  break;
402  case 'ProxyRestriction':
403  /* Currently ignored. */
404  break;
405  default:
406  throw new \Exception('Unknown condition: ' . var_export($node->localName, true));
407  }
408  }
409  }
410 
418  {
419  $authnStatements = Utils::xpQuery($xml, './saml_assertion:AuthnStatement');
420  if (empty($authnStatements)) {
421  $this->authnInstant = null;
422 
423  return;
424  } elseif (count($authnStatements) > 1) {
425  throw new \Exception('More than one <saml:AuthnStatement> in <saml:Assertion> not supported.');
426  }
427  $authnStatement = $authnStatements[0];
428 
429  if (!$authnStatement->hasAttribute('AuthnInstant')) {
430  throw new \Exception('Missing required AuthnInstant attribute on <saml:AuthnStatement>.');
431  }
432  $this->authnInstant = Utils::xsDateTimeToTimestamp($authnStatement->getAttribute('AuthnInstant'));
433 
434  if ($authnStatement->hasAttribute('SessionNotOnOrAfter')) {
435  $this->sessionNotOnOrAfter = Utils::xsDateTimeToTimestamp($authnStatement->getAttribute('SessionNotOnOrAfter'));
436  }
437 
438  if ($authnStatement->hasAttribute('SessionIndex')) {
439  $this->sessionIndex = $authnStatement->getAttribute('SessionIndex');
440  }
441 
442  $this->parseAuthnContext($authnStatement);
443  }
444 
451  private function parseAuthnContext(\DOMElement $authnStatementEl)
452  {
453  // Get the AuthnContext element
454  $authnContexts = Utils::xpQuery($authnStatementEl, './saml_assertion:AuthnContext');
455  if (count($authnContexts) > 1) {
456  throw new \Exception('More than one <saml:AuthnContext> in <saml:AuthnStatement>.');
457  } elseif (empty($authnContexts)) {
458  throw new \Exception('Missing required <saml:AuthnContext> in <saml:AuthnStatement>.');
459  }
460  $authnContextEl = $authnContexts[0];
461 
462  // Get the AuthnContextDeclRef (if available)
463  $authnContextDeclRefs = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextDeclRef');
464  if (count($authnContextDeclRefs) > 1) {
465  throw new \Exception(
466  'More than one <saml:AuthnContextDeclRef> found?'
467  );
468  } elseif (count($authnContextDeclRefs) === 1) {
469  $this->setAuthnContextDeclRef(trim($authnContextDeclRefs[0]->textContent));
470  }
471 
472  // Get the AuthnContextDecl (if available)
473  $authnContextDecls = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextDecl');
474  if (count($authnContextDecls) > 1) {
475  throw new \Exception(
476  'More than one <saml:AuthnContextDecl> found?'
477  );
478  } elseif (count($authnContextDecls) === 1) {
479  $this->setAuthnContextDecl(new Chunk($authnContextDecls[0]));
480  }
481 
482  // Get the AuthnContextClassRef (if available)
483  $authnContextClassRefs = Utils::xpQuery($authnContextEl, './saml_assertion:AuthnContextClassRef');
484  if (count($authnContextClassRefs) > 1) {
485  throw new \Exception('More than one <saml:AuthnContextClassRef> in <saml:AuthnContext>.');
486  } elseif (count($authnContextClassRefs) === 1) {
487  $this->setAuthnContextClassRef(trim($authnContextClassRefs[0]->textContent));
488  }
489 
490  // Constraint from XSD: MUST have one of the three
491  if (empty($this->authnContextClassRef) && empty($this->authnContextDecl) && empty($this->authnContextDeclRef)) {
492  throw new \Exception(
493  'Missing either <saml:AuthnContextClassRef> or <saml:AuthnContextDeclRef> or <saml:AuthnContextDecl>'
494  );
495  }
496 
497  $this->AuthenticatingAuthority = Utils::extractStrings(
498  $authnContextEl,
499  Constants::NS_SAML,
500  'AuthenticatingAuthority'
501  );
502  }
503 
510  private function parseAttributes(\DOMElement $xml)
511  {
512  $firstAttribute = true;
513  $attributes = Utils::xpQuery($xml, './saml_assertion:AttributeStatement/saml_assertion:Attribute');
514  foreach ($attributes as $attribute) {
515  if (!$attribute->hasAttribute('Name')) {
516  throw new \Exception('Missing name on <saml:Attribute> element.');
517  }
518  $name = $attribute->getAttribute('Name');
519 
520  if ($attribute->hasAttribute('NameFormat')) {
521  $nameFormat = $attribute->getAttribute('NameFormat');
522  } else {
523  $nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
524  }
525 
526  if ($firstAttribute) {
527  $this->nameFormat = $nameFormat;
528  $firstAttribute = false;
529  } else {
530  if ($this->nameFormat !== $nameFormat) {
531  $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
532  }
533  }
534 
535  if (!array_key_exists($name, $this->attributes)) {
536  $this->attributes[$name] = array();
537  $this->attributesValueTypes[$name] = array();
538  }
539 
540  $this->parseAttributeValue($attribute, $name);
541  }
542  }
543 
548  private function parseAttributeValue($attribute, $attributeName)
549  {
551  $values = Utils::xpQuery($attribute, './saml_assertion:AttributeValue');
552 
553  if ($attributeName === Constants::EPTI_URN_MACE || $attributeName === Constants::EPTI_URN_OID) {
554  foreach ($values as $index => $eptiAttributeValue) {
555  $eptiNameId = Utils::xpQuery($eptiAttributeValue, './saml_assertion:NameID');
556 
557  if (count($eptiNameId) !== 1) {
558  throw new RuntimeException(sprintf(
559  'A "%s" (EPTI) attribute value must be a NameID, none found for value no. "%d"',
560  $attributeName,
561  $index
562  ));
563  }
564 
565  $this->attributes[$attributeName][] = new XML\saml\NameID($eptiNameId[0]);
566  }
567 
568  return;
569  }
570 
571  foreach ($values as $value) {
572  $hasNonTextChildElements = false;
573  foreach ($value->childNodes as $childNode) {
575  if ($childNode->nodeType !== XML_TEXT_NODE) {
576  $hasNonTextChildElements = true;
577  break;
578  }
579  }
580 
581  $type = $value->getAttribute('xsi:type');
582  if ($type === '') {
583  $type = null;
584  }
585  $this->attributesValueTypes[$attributeName][] = $type;
586 
587  if ($hasNonTextChildElements) {
588  $this->attributes[$attributeName][] = $value->childNodes;
589  continue;
590  }
591 
592  if ($type === 'xs:integer') {
593  $this->attributes[$attributeName][] = (int)$value->textContent;
594  } else {
595  $this->attributes[$attributeName][] = trim($value->textContent);
596  }
597  }
598  }
599 
606  {
607  $this->encryptedAttributes = Utils::xpQuery(
608  $xml,
609  './saml_assertion:AttributeStatement/saml_assertion:EncryptedAttribute'
610  );
611  }
612 
618  private function parseSignature(\DOMElement $xml)
619  {
621  $signatureMethod = Utils::xpQuery($xml, './ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm');
622 
623  /* Validate the signature element of the message. */
624  $sig = Utils::validateElement($xml);
625  if ($sig !== false) {
626  $this->wasSignedAtConstruction = true;
627  $this->certificates = $sig['Certificates'];
628  $this->signatureData = $sig;
629  $this->signatureMethod = $signatureMethod[0]->value;
630  }
631  }
632 
643  public function validate(XMLSecurityKey $key)
644  {
645  assert($key->type === \RobRichards\XMLSecLibs\XMLSecurityKey::RSA_SHA1);
646 
647  if ($this->signatureData === null) {
648  return false;
649  }
650 
651  Utils::validateSignature($this->signatureData, $key);
652 
653  return true;
654  }
655 
661  public function getId()
662  {
663  return $this->id;
664  }
665 
671  public function setId($id)
672  {
673  assert(is_string($id));
674 
675  $this->id = $id;
676  }
677 
683  public function getIssueInstant()
684  {
685  return $this->issueInstant;
686  }
687 
693  public function setIssueInstant($issueInstant)
694  {
695  assert(is_int($issueInstant));
696 
697  $this->issueInstant = $issueInstant;
698  }
699 
705  public function getIssuer()
706  {
707  return $this->issuer;
708  }
709 
715  public function setIssuer($issuer)
716  {
717  assert(is_string($issuer) || $issuer instanceof XML\saml\Issuer);
718 
719  $this->issuer = $issuer;
720  }
721 
728  public function getNameId()
729  {
730  if ($this->encryptedNameId !== null) {
731  throw new \Exception('Attempted to retrieve encrypted NameID without decrypting it first.');
732  }
733 
734  return $this->nameId;
735  }
736 
746  public function setNameId($nameId)
747  {
748  assert(is_array($nameId) || is_null($nameId) || $nameId instanceof XML\saml\NameID);
749 
750  if (is_array($nameId)) {
751  $nameId = XML\saml\NameID::fromArray($nameId);
752  }
753  $this->nameId = $nameId;
754  }
755 
761  public function isNameIdEncrypted()
762  {
763  return $this->encryptedNameId !== null;
764  }
765 
772  {
773  /* First create a XML representation of the NameID. */
774  $doc = DOMDocumentFactory::create();
775  $root = $doc->createElement('root');
776  $doc->appendChild($root);
777  $this->nameId->toXML($root);
778  $nameId = $root->firstChild;
779 
780  Utils::getContainer()->debugMessage($nameId, 'encrypt');
781 
782  /* Encrypt the NameID. */
783  $enc = new XMLSecEnc();
784  $enc->setNode($nameId);
785  // @codingStandardsIgnoreStart
786  $enc->type = XMLSecEnc::Element;
787  // @codingStandardsIgnoreEnd
788 
789  $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
790  $symmetricKey->generateSessionKey();
791  $enc->encryptKey($key, $symmetricKey);
792 
793  $this->encryptedNameId = $enc->encryptNode($symmetricKey);
794  $this->nameId = null;
795  }
796 
803  public function decryptNameId(XMLSecurityKey $key, array $blacklist = array())
804  {
805  if ($this->encryptedNameId === null) {
806  /* No NameID to decrypt. */
807 
808  return;
809  }
810 
811  $nameId = Utils::decryptElement($this->encryptedNameId, $key, $blacklist);
812  Utils::getContainer()->debugMessage($nameId, 'decrypt');
813  $this->nameId = new XML\saml\NameID($nameId);
814 
815  $this->encryptedNameId = null;
816  }
817 
823  public function hasEncryptedAttributes()
824  {
825  return $this->encryptedAttributes !== null;
826  }
827 
835  public function decryptAttributes(XMLSecurityKey $key, array $blacklist = array())
836  {
837  if ($this->encryptedAttributes === null) {
838  return;
839  }
840  $firstAttribute = true;
841  $attributes = $this->encryptedAttributes;
842  foreach ($attributes as $attributeEnc) {
843  /*Decrypt node <EncryptedAttribute>*/
844  $attribute = Utils::decryptElement(
845  $attributeEnc->getElementsByTagName('EncryptedData')->item(0),
846  $key,
847  $blacklist
848  );
849 
850  if (!$attribute->hasAttribute('Name')) {
851  throw new \Exception('Missing name on <saml:Attribute> element.');
852  }
853  $name = $attribute->getAttribute('Name');
854 
855  if ($attribute->hasAttribute('NameFormat')) {
856  $nameFormat = $attribute->getAttribute('NameFormat');
857  } else {
858  $nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
859  }
860 
861  if ($firstAttribute) {
862  $this->nameFormat = $nameFormat;
863  $firstAttribute = false;
864  } else {
865  if ($this->nameFormat !== $nameFormat) {
866  $this->nameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
867  }
868  }
869 
870  if (!array_key_exists($name, $this->attributes)) {
871  $this->attributes[$name] = array();
872  }
873 
874  $this->parseAttributeValue($attribute, $name);
875  }
876  }
877 
886  public function getNotBefore()
887  {
888  return $this->notBefore;
889  }
890 
898  public function setNotBefore($notBefore)
899  {
900  assert(is_int($notBefore) || is_null($notBefore));
901 
902  $this->notBefore = $notBefore;
903  }
904 
913  public function getNotOnOrAfter()
914  {
915  return $this->notOnOrAfter;
916  }
917 
925  public function setNotOnOrAfter($notOnOrAfter)
926  {
927  assert(is_int($notOnOrAfter) || is_null($notOnOrAfter));
928 
929  $this->notOnOrAfter = $notOnOrAfter;
930  }
931 
937  public function setEncryptedAttributes($ea)
938  {
939  $this->requiredEncAttributes = $ea;
940  }
941 
949  public function getValidAudiences()
950  {
951  return $this->validAudiences;
952  }
953 
961  public function setValidAudiences(array $validAudiences = null)
962  {
963  $this->validAudiences = $validAudiences;
964  }
965 
971  public function getAuthnInstant()
972  {
973  return $this->authnInstant;
974  }
975 
976 
982  public function setAuthnInstant($authnInstant)
983  {
984  assert(is_int($authnInstant) || is_null($authnInstant));
985 
986  $this->authnInstant = $authnInstant;
987  }
988 
997  public function getSessionNotOnOrAfter()
998  {
999  return $this->sessionNotOnOrAfter;
1000  }
1001 
1009  public function setSessionNotOnOrAfter($sessionNotOnOrAfter)
1010  {
1011  assert(is_int($sessionNotOnOrAfter) || is_null($sessionNotOnOrAfter));
1012 
1013  $this->sessionNotOnOrAfter = $sessionNotOnOrAfter;
1014  }
1015 
1021  public function getSessionIndex()
1022  {
1023  return $this->sessionIndex;
1024  }
1025 
1035  {
1036  assert(is_string($sessionIndex) || is_null($sessionIndex));
1037 
1038  $this->sessionIndex = $sessionIndex;
1039  }
1040 
1055  public function getAuthnContext()
1056  {
1057  if (!empty($this->authnContextClassRef)) {
1058  return $this->authnContextClassRef;
1059  }
1060  if (!empty($this->authnContextDeclRef)) {
1061  return $this->authnContextDeclRef;
1062  }
1063  return null;
1064  }
1065 
1075  public function setAuthnContext($authnContext)
1076  {
1077  $this->setAuthnContextClassRef($authnContext);
1078  }
1079 
1088  public function getAuthnContextClassRef()
1089  {
1090  return $this->authnContextClassRef;
1091  }
1092 
1101  public function setAuthnContextClassRef($authnContextClassRef)
1102  {
1103  assert(is_string($authnContextClassRef) || is_null($authnContextClassRef));
1104 
1105  $this->authnContextClassRef = $authnContextClassRef;
1106  }
1107 
1114  public function setAuthnContextDecl(Chunk $authnContextDecl)
1115  {
1116  if (!empty($this->authnContextDeclRef)) {
1117  throw new \Exception(
1118  'AuthnContextDeclRef is already registered! May only have either a Decl or a DeclRef, not both!'
1119  );
1120  }
1121 
1122  $this->authnContextDecl = $authnContextDecl;
1123  }
1124 
1133  public function getAuthnContextDecl()
1134  {
1135  return $this->authnContextDecl;
1136  }
1137 
1144  public function setAuthnContextDeclRef($authnContextDeclRef)
1145  {
1146  if (!empty($this->authnContextDecl)) {
1147  throw new \Exception(
1148  'AuthnContextDecl is already registered! May only have either a Decl or a DeclRef, not both!'
1149  );
1150  }
1151 
1152  $this->authnContextDeclRef = $authnContextDeclRef;
1153  }
1154 
1163  public function getAuthnContextDeclRef()
1164  {
1165  return $this->authnContextDeclRef;
1166  }
1167 
1174  public function getAuthenticatingAuthority()
1175  {
1176  return $this->AuthenticatingAuthority;
1177  }
1178 
1186  {
1187  $this->AuthenticatingAuthority = $authenticatingAuthority;
1188  }
1189 
1195  public function getAttributes()
1196  {
1197  return $this->attributes;
1198  }
1199 
1206  {
1207  $this->attributes = $attributes;
1208  }
1209 
1215  public function getAttributesValueTypes()
1216  {
1217  return $this->attributesValueTypes;
1218  }
1219 
1225  public function setAttributesValueTypes(array $attributesValueTypes)
1226  {
1227  $this->attributesValueTypes = $attributesValueTypes;
1228  }
1229 
1238  public function getAttributeNameFormat()
1239  {
1240  return $this->nameFormat;
1241  }
1242 
1248  public function setAttributeNameFormat($nameFormat)
1249  {
1250  assert(is_string($nameFormat));
1251 
1252  $this->nameFormat = $nameFormat;
1253  }
1254 
1260  public function getSubjectConfirmation()
1261  {
1262  return $this->SubjectConfirmation;
1263  }
1264 
1270  public function setSubjectConfirmation(array $SubjectConfirmation)
1271  {
1272  $this->SubjectConfirmation = $SubjectConfirmation;
1273  }
1274 
1280  public function getSignatureKey()
1281  {
1282  return $this->signatureKey;
1283  }
1284 
1292  public function setSignatureKey(XMLSecurityKey $signatureKey = null)
1293  {
1294  $this->signatureKey = $signatureKey;
1295  }
1296 
1303  public function getEncryptionKey()
1304  {
1305  return $this->encryptionKey;
1306  }
1307 
1313  public function setEncryptionKey(XMLSecurityKey $Key = null)
1314  {
1315  $this->encryptionKey = $Key;
1316  }
1317 
1326  {
1327  $this->certificates = $certificates;
1328  }
1329 
1335  public function getCertificates()
1336  {
1337  return $this->certificates;
1338  }
1339 
1343  public function getWasSignedAtConstruction()
1344  {
1345  return $this->wasSignedAtConstruction;
1346  }
1347 
1351  public function getSignatureMethod()
1352  {
1353  return $this->signatureMethod;
1354  }
1355 
1362  public function toXML(\DOMNode $parentElement = null)
1363  {
1364  if ($parentElement === null) {
1365  $document = DOMDocumentFactory::create();
1366  $parentElement = $document;
1367  } else {
1368  $document = $parentElement->ownerDocument;
1369  }
1370 
1371  $root = $document->createElementNS(Constants::NS_SAML, 'saml:' . 'Assertion');
1372  $parentElement->appendChild($root);
1373 
1374  /* Ugly hack to add another namespace declaration to the root element. */
1375  $root->setAttributeNS(Constants::NS_SAMLP, 'samlp:tmp', 'tmp');
1376  $root->removeAttributeNS(Constants::NS_SAMLP, 'tmp');
1377  $root->setAttributeNS(Constants::NS_XSI, 'xsi:tmp', 'tmp');
1378  $root->removeAttributeNS(Constants::NS_XSI, 'tmp');
1379  $root->setAttributeNS(Constants::NS_XS, 'xs:tmp', 'tmp');
1380  $root->removeAttributeNS(Constants::NS_XS, 'tmp');
1381 
1382  $root->setAttribute('ID', $this->id);
1383  $root->setAttribute('Version', '2.0');
1384  $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
1385 
1386  if (is_string($this->issuer)) {
1387  $issuer = Utils::addString($root, Constants::NS_SAML, 'saml:Issuer', $this->issuer);
1388  } elseif ($this->issuer instanceof XML\saml\Issuer) {
1389  $issuer = $this->issuer->toXML($root);
1390  }
1391 
1392  $this->addSubject($root);
1393  $this->addConditions($root);
1394  $this->addAuthnStatement($root);
1395  if ($this->requiredEncAttributes === false) {
1396  $this->addAttributeStatement($root);
1397  } else {
1398  $this->addEncryptedAttributeStatement($root);
1399  }
1400 
1401  if ($this->signatureKey !== null) {
1402  Utils::insertSignature($this->signatureKey, $this->certificates, $root, $issuer->nextSibling);
1403  }
1404 
1405  return $root;
1406  }
1407 
1413  private function addSubject(\DOMElement $root)
1414  {
1415  if ($this->nameId === null && $this->encryptedNameId === null) {
1416  /* We don't have anything to create a Subject node for. */
1417 
1418  return;
1419  }
1420 
1421  $subject = $root->ownerDocument->createElementNS(Constants::NS_SAML, 'saml:Subject');
1422  $root->appendChild($subject);
1423 
1424  if ($this->encryptedNameId === null) {
1425  $this->nameId->toXML($subject);
1426  } else {
1427  $eid = $subject->ownerDocument->createElementNS(Constants::NS_SAML, 'saml:' . 'EncryptedID');
1428  $subject->appendChild($eid);
1429  $eid->appendChild($subject->ownerDocument->importNode($this->encryptedNameId, true));
1430  }
1431 
1432  foreach ($this->SubjectConfirmation as $sc) {
1433  $sc->toXML($subject);
1434  }
1435  }
1436 
1437 
1443  private function addConditions(\DOMElement $root)
1444  {
1445  $document = $root->ownerDocument;
1446 
1447  $conditions = $document->createElementNS(Constants::NS_SAML, 'saml:Conditions');
1448  $root->appendChild($conditions);
1449 
1450  if ($this->notBefore !== null) {
1451  $conditions->setAttribute('NotBefore', gmdate('Y-m-d\TH:i:s\Z', $this->notBefore));
1452  }
1453  if ($this->notOnOrAfter !== null) {
1454  $conditions->setAttribute('NotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->notOnOrAfter));
1455  }
1456 
1457  if ($this->validAudiences !== null) {
1458  $ar = $document->createElementNS(Constants::NS_SAML, 'saml:AudienceRestriction');
1459  $conditions->appendChild($ar);
1460 
1461  Utils::addStrings($ar, Constants::NS_SAML, 'saml:Audience', false, $this->validAudiences);
1462  }
1463  }
1464 
1465 
1471  private function addAuthnStatement(\DOMElement $root)
1472  {
1473  if ($this->authnInstant === null ||
1474  (
1475  $this->authnContextClassRef === null &&
1476  $this->authnContextDecl === null &&
1477  $this->authnContextDeclRef === null
1478  )
1479  ) {
1480  /* No authentication context or AuthnInstant => no authentication statement. */
1481 
1482  return;
1483  }
1484 
1485  $document = $root->ownerDocument;
1486 
1487  $authnStatementEl = $document->createElementNS(Constants::NS_SAML, 'saml:AuthnStatement');
1488  $root->appendChild($authnStatementEl);
1489 
1490  $authnStatementEl->setAttribute('AuthnInstant', gmdate('Y-m-d\TH:i:s\Z', $this->authnInstant));
1491 
1492  if ($this->sessionNotOnOrAfter !== null) {
1493  $authnStatementEl->setAttribute('SessionNotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->sessionNotOnOrAfter));
1494  }
1495  if ($this->sessionIndex !== null) {
1496  $authnStatementEl->setAttribute('SessionIndex', $this->sessionIndex);
1497  }
1498 
1499  $authnContextEl = $document->createElementNS(Constants::NS_SAML, 'saml:AuthnContext');
1500  $authnStatementEl->appendChild($authnContextEl);
1501 
1502  if (!empty($this->authnContextClassRef)) {
1503  Utils::addString(
1504  $authnContextEl,
1505  Constants::NS_SAML,
1506  'saml:AuthnContextClassRef',
1507  $this->authnContextClassRef
1508  );
1509  }
1510  if (!empty($this->authnContextDecl)) {
1511  $this->authnContextDecl->toXML($authnContextEl);
1512  }
1513  if (!empty($this->authnContextDeclRef)) {
1514  Utils::addString(
1515  $authnContextEl,
1516  Constants::NS_SAML,
1517  'saml:AuthnContextDeclRef',
1518  $this->authnContextDeclRef
1519  );
1520  }
1521 
1522  Utils::addStrings(
1523  $authnContextEl,
1524  Constants::NS_SAML,
1525  'saml:AuthenticatingAuthority',
1526  false,
1527  $this->AuthenticatingAuthority
1528  );
1529  }
1530 
1531 
1537  private function addAttributeStatement(\DOMElement $root)
1538  {
1539  if (empty($this->attributes)) {
1540  return;
1541  }
1542 
1543  $document = $root->ownerDocument;
1544 
1545  $attributeStatement = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeStatement');
1546  $root->appendChild($attributeStatement);
1547 
1548  foreach ($this->attributes as $name => $values) {
1549  $attribute = $document->createElementNS(Constants::NS_SAML, 'saml:Attribute');
1550  $attributeStatement->appendChild($attribute);
1551  $attribute->setAttribute('Name', $name);
1552 
1553  if ($this->nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
1554  $attribute->setAttribute('NameFormat', $this->nameFormat);
1555  }
1556 
1557  // make sure eduPersonTargetedID can be handled properly as a NameID
1558  if ($name === Constants::EPTI_URN_MACE || $name === Constants::EPTI_URN_OID) {
1559  foreach ($values as $eptiValue) {
1560  $attributeValue = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1561  $attribute->appendChild($attributeValue);
1562  if ($eptiValue instanceof XML\saml\NameID) {
1563  $eptiValue->toXML($attributeValue);
1564  } elseif ($eptiValue instanceof \DOMNodeList) {
1565  $node = $root->ownerDocument->importNode($eptiValue->item(0), true);
1566  $attributeValue->appendChild($node);
1567  } else {
1568  $attributeValue->textContent = $eptiValue;
1569  }
1570  }
1571 
1572  continue;
1573  }
1574 
1575  // get value type(s) for the current attribute
1576  if (is_array($this->attributesValueTypes) && array_key_exists($name, $this->attributesValueTypes)) {
1577  $valueTypes = $this->attributesValueTypes[$name];
1578  if (is_array($valueTypes) && count($valueTypes) != count($values)) {
1579  throw new \Exception('Array of value types and array of values have different size for attribute '. var_export($name, true));
1580  }
1581  } else {
1582  // if no type(s), default behaviour
1583  $valueTypes = null;
1584  }
1585 
1586  $vidx = -1;
1587  foreach ($values as $value) {
1588  $vidx++;
1589 
1590  // try to get type from current types
1591  $type = null;
1592  if (!is_null($valueTypes)) {
1593  if (is_array($valueTypes)) {
1594  $type = $valueTypes[$vidx];
1595  } else {
1596  $type = $valueTypes;
1597  }
1598  }
1599 
1600  // if no type get from types, use default behaviour
1601  if (is_null($type)) {
1602  if (is_string($value)) {
1603  $type = 'xs:string';
1604  } elseif (is_int($value)) {
1605  $type = 'xs:integer';
1606  } else {
1607  $type = null;
1608  }
1609  }
1610 
1611  $attributeValue = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1612  $attribute->appendChild($attributeValue);
1613  if ($type !== null) {
1614  $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:type', $type);
1615  }
1616  if (is_null($value)) {
1617  $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:nil', 'true');
1618  }
1619 
1620  if ($value instanceof \DOMNodeList) {
1621  for ($i = 0; $i < $value->length; $i++) {
1622  $node = $document->importNode($value->item($i), true);
1623  $attributeValue->appendChild($node);
1624  }
1625  } else {
1626  $attributeValue->appendChild($document->createTextNode($value));
1627  }
1628  }
1629  }
1630  }
1631 
1632 
1639  {
1640  if ($this->requiredEncAttributes === false) {
1641  return;
1642  }
1643 
1644  $document = $root->ownerDocument;
1645 
1646  $attributeStatement = $document->createElementNS(Constants::NS_SAML, 'saml:AttributeStatement');
1647  $root->appendChild($attributeStatement);
1648 
1649  foreach ($this->attributes as $name => $values) {
1650  $document2 = DOMDocumentFactory::create();
1651  $attribute = $document2->createElementNS(Constants::NS_SAML, 'saml:Attribute');
1652  $attribute->setAttribute('Name', $name);
1653  $document2->appendChild($attribute);
1654 
1655  if ($this->nameFormat !== Constants::NAMEFORMAT_UNSPECIFIED) {
1656  $attribute->setAttribute('NameFormat', $this->nameFormat);
1657  }
1658 
1659  foreach ($values as $value) {
1660  if (is_string($value)) {
1661  $type = 'xs:string';
1662  } elseif (is_int($value)) {
1663  $type = 'xs:integer';
1664  } else {
1665  $type = null;
1666  }
1667 
1668  $attributeValue = $document2->createElementNS(Constants::NS_SAML, 'saml:AttributeValue');
1669  $attribute->appendChild($attributeValue);
1670  if ($type !== null) {
1671  $attributeValue->setAttributeNS(Constants::NS_XSI, 'xsi:type', $type);
1672  }
1673 
1674  if ($value instanceof \DOMNodeList) {
1675  for ($i = 0; $i < $value->length; $i++) {
1676  $node = $document2->importNode($value->item($i), true);
1677  $attributeValue->appendChild($node);
1678  }
1679  } else {
1680  $attributeValue->appendChild($document2->createTextNode($value));
1681  }
1682  }
1683  /*Once the attribute nodes are built, the are encrypted*/
1684  $EncAssert = new XMLSecEnc();
1685  $EncAssert->setNode($document2->documentElement);
1686  $EncAssert->type = 'http://www.w3.org/2001/04/xmlenc#Element';
1687  /*
1688  * Attributes are encrypted with a session key and this one with
1689  * $EncryptionKey
1690  */
1691  $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES256_CBC);
1692  $symmetricKey->generateSessionKey();
1693  $EncAssert->encryptKey($this->encryptionKey, $symmetricKey);
1694  $EncrNode = $EncAssert->encryptNode($symmetricKey);
1695 
1696  $EncAttribute = $document->createElementNS(Constants::NS_SAML, 'saml:EncryptedAttribute');
1697  $attributeStatement->appendChild($EncAttribute);
1698  $n = $document->importNode($EncrNode, true);
1699  $EncAttribute->appendChild($n);
1700  }
1701  }
1702 }
addAuthnStatement(\DOMElement $root)
Add a AuthnStatement-node to the assertion.
Definition: Assertion.php:1471
setAttributes(array $attributes)
Replace all attributes.
Definition: Assertion.php:1205
getAuthnContextClassRef()
Retrieve the authentication method used to authenticate the user.
Definition: Assertion.php:1088
parseAuthnStatement(\DOMElement $xml)
Parse AuthnStatement in assertion.
Definition: Assertion.php:417
addSubject(\DOMElement $root)
Add a Subject-node to the assertion.
Definition: Assertion.php:1413
setSessionNotOnOrAfter($sessionNotOnOrAfter)
Set the session expiration timestamp.
Definition: Assertion.php:1009
decryptNameId(XMLSecurityKey $key, array $blacklist=array())
Decrypt the NameId of the subject in the assertion.
Definition: Assertion.php:803
getAuthnContextDeclRef()
Get the authentication context declaration reference.
Definition: Assertion.php:1163
setAuthnContextDeclRef($authnContextDeclRef)
Set the authentication context declaration reference.
Definition: Assertion.php:1144
$type
getIssuer()
Retrieve the issuer if this assertion.
Definition: Assertion.php:705
getAttributesValueTypes()
Retrieve all attributes value types.
Definition: Assertion.php:1215
getAttributeNameFormat()
Retrieve the NameFormat used on all attributes.
Definition: Assertion.php:1238
setNameId($nameId)
Set the NameId of the subject in the assertion.
Definition: Assertion.php:746
getAuthnContextDecl()
Get the authentication context declaration.
Definition: Assertion.php:1133
getCertificates()
Retrieve the certificates that are included in the assertion.
Definition: Assertion.php:1335
setAuthnContextDecl(Chunk $authnContextDecl)
Set the authentication context declaration.
Definition: Assertion.php:1114
if(!array_key_exists('StateId', $_REQUEST)) $id
setAuthnInstant($authnInstant)
Set the AuthnInstant of the assertion.
Definition: Assertion.php:982
$sessionIndex
Definition: saml2-acs.php:139
setEncryptionKey(XMLSecurityKey $Key=null)
Set the private key we should use to encrypt the attributes.
Definition: Assertion.php:1313
$attributes
setValidAudiences(array $validAudiences=null)
Set the audiences that are allowed to receive this assertion.
Definition: Assertion.php:961
$certificates
Definition: metarefresh.php:39
$index
Definition: metadata.php:60
getEncryptionKey()
Return the key we should use to encrypt the assertion.
Definition: Assertion.php:1303
setAuthnContextClassRef($authnContextClassRef)
Set the authentication method used to authenticate the user.
Definition: Assertion.php:1101
setSubjectConfirmation(array $SubjectConfirmation)
Set the SubjectConfirmation elements that should be included in the assertion.
Definition: Assertion.php:1270
setAttributesValueTypes(array $attributesValueTypes)
Replace all attributes value types.
Definition: Assertion.php:1225
$nameId
Definition: saml2-acs.php:138
encryptNameId(XMLSecurityKey $key)
Encrypt the NameID in the Assertion.
Definition: Assertion.php:771
setAuthenticatingAuthority($authenticatingAuthority)
Set the AuthenticatingAuthority.
Definition: Assertion.php:1185
getAuthenticatingAuthority()
Retrieve the AuthenticatingAuthority.
Definition: Assertion.php:1174
setCertificates(array $certificates)
Set the certificates that should be included in the assertion.
Definition: Assertion.php:1325
getAttributes()
Retrieve all attributes.
Definition: Assertion.php:1195
$xml
Definition: metadata.php:240
parseAttributes(\DOMElement $xml)
Parse attribute statements in assertion.
Definition: Assertion.php:510
setSignatureKey(XMLSecurityKey $signatureKey=null)
Set the private key we should use to sign the assertion.
Definition: Assertion.php:1292
if($format !==null) $name
Definition: metadata.php:146
getSessionNotOnOrAfter()
Retrieve the session expiration timestamp.
Definition: Assertion.php:997
setNotOnOrAfter($notOnOrAfter)
Set the expiration timestamp of this assertion.
Definition: Assertion.php:925
setAttributeNameFormat($nameFormat)
Set the NameFormat used on all attributes.
Definition: Assertion.php:1248
getValidAudiences()
Retrieve the audiences that are allowed to receive this assertion.
Definition: Assertion.php:949
getNotOnOrAfter()
Retrieve the expiration timestamp of this assertion.
Definition: Assertion.php:913
isNameIdEncrypted()
Check whether the NameId is encrypted.
Definition: Assertion.php:761
getId()
Retrieve the identifier of this assertion.
Definition: Assertion.php:661
parseEncryptedAttributes(\DOMElement $xml)
Parse encrypted attribute statements in assertion.
Definition: Assertion.php:605
toXML(\DOMNode $parentElement=null)
Convert this assertion to an XML element.
Definition: Assertion.php:1362
getSessionIndex()
Retrieve the session index of the user at the IdP.
Definition: Assertion.php:1021
getSignatureKey()
Retrieve the private key we should use to sign the assertion.
Definition: Assertion.php:1280
hasEncryptedAttributes()
Did this Assertion contain encrypted Attributes?
Definition: Assertion.php:823
getWasSignedAtConstruction()
Definition: Assertion.php:1343
$n
Definition: RandomTest.php:85
setAuthnContext($authnContext)
Set the authentication method used to authenticate the user.
Definition: Assertion.php:1075
catch(Exception $e) if(!($request instanceof \SAML2\ArtifactResolve)) $issuer
catch(sspmod_saml_Error $e) $authenticatingAuthority
Definition: saml2-acs.php:137
Create styles array
The data for the language used.
parseSubject(\DOMElement $xml)
Parse subject in assertion.
Definition: Assertion.php:309
getAuthnInstant()
Retrieve the AuthnInstant of the assertion.
Definition: Assertion.php:971
validate(XMLSecurityKey $key)
Validate this assertion against a public key.
Definition: Assertion.php:643
parseAuthnContext(\DOMElement $authnStatementEl)
Parse AuthnContext in AuthnStatement.
Definition: Assertion.php:451
setNotBefore($notBefore)
Set the earliest timestamp this assertion can be used.
Definition: Assertion.php:898
decryptAttributes(XMLSecurityKey $key, array $blacklist=array())
Decrypt the assertion attributes.
Definition: Assertion.php:835
addEncryptedAttributeStatement(\DOMElement $root)
Add an EncryptedAttribute Statement-node to the assertion.
Definition: Assertion.php:1638
setSessionIndex($sessionIndex)
Set the session index of the user at the IdP.
Definition: Assertion.php:1034
getSubjectConfirmation()
Retrieve the SubjectConfirmation elements we have in our Subject element.
Definition: Assertion.php:1260
__construct(\DOMElement $xml=null)
Constructor for SAML 2 assertions.
Definition: Assertion.php:257
getAuthnContext()
Retrieve the authentication method used to authenticate the user.
Definition: Assertion.php:1055
$i
Definition: disco.tpl.php:19
getIssueInstant()
Retrieve the issue timestamp of this assertion.
Definition: Assertion.php:683
parseConditions(\DOMElement $xml)
Parse conditions in assertion.
Definition: Assertion.php:353
getNotBefore()
Retrieve the earliest timestamp this assertion is valid.
Definition: Assertion.php:886
addAttributeStatement(\DOMElement $root)
Add an AttributeStatement-node to the assertion.
Definition: Assertion.php:1537
addConditions(\DOMElement $root)
Add a Conditions-node to the assertion.
Definition: Assertion.php:1443
setIssuer($issuer)
Set the issuer of this message.
Definition: Assertion.php:715
setEncryptedAttributes($ea)
Set $EncryptedAttributes if attributes will send encrypted.
Definition: Assertion.php:937
$key
Definition: croninfo.php:18
setIssueInstant($issueInstant)
Set the issue timestamp of this assertion.
Definition: Assertion.php:693
setId($id)
Set the identifier of this assertion.
Definition: Assertion.php:671
getNameId()
Retrieve the NameId of the subject in the assertion.
Definition: Assertion.php:728