ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
AuthnResponse.php
Go to the documentation of this file.
1 <?php
2 
10 namespace SimpleSAML\XML\Shib13;
11 
13 use SAML2\Utils;
18 
20 {
21 
25  private $validator = null;
26 
27 
31  private $messageValidated = false;
32 
33 
34  const SHIB_PROTOCOL_NS = 'urn:oasis:names:tc:SAML:1.0:protocol';
35  const SHIB_ASSERT_NS = 'urn:oasis:names:tc:SAML:1.0:assertion';
36 
37 
41  private $dom;
42 
46  private $relayState = null;
47 
48 
55  {
56  assert('is_bool($messageValidated)');
57 
58  $this->messageValidated = $messageValidated;
59  }
60 
61 
62  public function setXML($xml)
63  {
64  assert('is_string($xml)');
65 
66  try {
67  $this->dom = DOMDocumentFactory::fromString(str_replace("\r", "", $xml));
68  } catch (\Exception $e) {
69  throw new \Exception('Unable to parse AuthnResponse XML.');
70  }
71  }
72 
73  public function setRelayState($relayState)
74  {
75  $this->relayState = $relayState;
76  }
77 
78  public function getRelayState()
79  {
80  return $this->relayState;
81  }
82 
83  public function validate()
84  {
85  assert('$this->dom instanceof DOMDocument');
86 
87  if ($this->messageValidated) {
88  // This message was validated externally
89  return true;
90  }
91 
92  // Validate the signature
93  $this->validator = new Validator($this->dom, array('ResponseID', 'AssertionID'));
94 
95  // Get the issuer of the response
96  $issuer = $this->getIssuer();
97 
98  // Get the metadata of the issuer
100  $md = $metadata->getMetaDataConfig($issuer, 'shib13-idp-remote');
101 
102  $publicKeys = $md->getPublicKeys('signing');
103  if ($publicKeys !== null) {
104  $certFingerprints = array();
105  foreach ($publicKeys as $key) {
106  if ($key['type'] !== 'X509Certificate') {
107  continue;
108  }
109  $certFingerprints[] = sha1(base64_decode($key['X509Certificate']));
110  }
111  $this->validator->validateFingerprint($certFingerprints);
112  } elseif ($md->hasValue('certFingerprint')) {
113  $certFingerprints = $md->getArrayizeString('certFingerprint');
114 
115  // Validate the fingerprint
116  $this->validator->validateFingerprint($certFingerprints);
117  } elseif ($md->hasValue('caFile')) {
118  // Validate against CA
119  $this->validator->validateCA(Config::getCertPath($md->getString('caFile')));
120  } else {
121  throw new \SimpleSAML_Error_Exception('Missing certificate in Shibboleth 1.3 IdP Remote metadata for identity provider [' . $issuer . '].');
122  }
123 
124  return true;
125  }
126 
127 
134  private function isNodeValidated($node)
135  {
136  if ($this->messageValidated) {
137  // This message was validated externally
138  return true;
139  }
140 
141  if ($this->validator === null) {
142  return false;
143  }
144 
145  // Convert the node to a DOM node if it is an element from SimpleXML
146  if ($node instanceof \SimpleXMLElement) {
147  $node = dom_import_simplexml($node);
148  }
149 
150  assert('$node instanceof DOMNode');
151 
152  return $this->validator->isNodeValidated($node);
153  }
154 
155 
164  private function doXPathQuery($query, $node = null)
165  {
166  assert('is_string($query)');
167  assert('$this->dom instanceof DOMDocument');
168 
169  if ($node === null) {
170  $node = $this->dom->documentElement;
171  }
172 
173  assert('$node instanceof DOMNode');
174 
175  $xPath = new \DOMXpath($this->dom);
176  $xPath->registerNamespace('shibp', self::SHIB_PROTOCOL_NS);
177  $xPath->registerNamespace('shib', self::SHIB_ASSERT_NS);
178 
179  return $xPath->query($query, $node);
180  }
181 
187  public function getSessionIndex()
188  {
189  assert('$this->dom instanceof DOMDocument');
190 
191  $query = '/shibp:Response/shib:Assertion/shib:AuthnStatement';
192  $nodelist = $this->doXPathQuery($query);
193  if ($node = $nodelist->item(0)) {
194  return $node->getAttribute('SessionIndex');
195  }
196 
197  return null;
198  }
199 
200 
201  public function getAttributes()
202  {
204  $md = $metadata->getMetadata($this->getIssuer(), 'shib13-idp-remote');
205  $base64 = isset($md['base64attributes']) ? $md['base64attributes'] : false;
206 
207  if (! ($this->dom instanceof \DOMDocument)) {
208  return array();
209  }
210 
211  $attributes = array();
212 
213  $assertions = $this->doXPathQuery('/shibp:Response/shib:Assertion');
214 
215  foreach ($assertions as $assertion) {
216  if (!$this->isNodeValidated($assertion)) {
217  throw new \Exception('Shib13 AuthnResponse contained an unsigned assertion.');
218  }
219 
220  $conditions = $this->doXPathQuery('shib:Conditions', $assertion);
221  if ($conditions && $conditions->length > 0) {
222  $condition = $conditions->item(0);
223 
224  $start = $condition->getAttribute('NotBefore');
225  $end = $condition->getAttribute('NotOnOrAfter');
226 
227  if ($start && $end) {
228  if (!self::checkDateConditions($start, $end)) {
229  error_log('Date check failed ... (from ' . $start . ' to ' . $end . ')');
230  continue;
231  }
232  }
233  }
234 
235  $attribute_nodes = $this->doXPathQuery('shib:AttributeStatement/shib:Attribute/shib:AttributeValue', $assertion);
237  foreach ($attribute_nodes as $attribute) {
238  $value = $attribute->textContent;
239  $name = $attribute->parentNode->getAttribute('AttributeName');
240 
241  if ($attribute->hasAttribute('Scope')) {
242  $scopePart = '@' . $attribute->getAttribute('Scope');
243  } else {
244  $scopePart = '';
245  }
246 
247  if (!is_string($name)) {
248  throw new \Exception('Shib13 Attribute node without an AttributeName.');
249  }
250 
251  if (!array_key_exists($name, $attributes)) {
252  $attributes[$name] = array();
253  }
254 
255  if ($base64) {
256  $encodedvalues = explode('_', $value);
257  foreach ($encodedvalues as $v) {
258  $attributes[$name][] = base64_decode($v) . $scopePart;
259  }
260  } else {
261  $attributes[$name][] = $value . $scopePart;
262  }
263  }
264  }
265 
266  return $attributes;
267  }
268 
269 
270  public function getIssuer()
271  {
272  $query = '/shibp:Response/shib:Assertion/@Issuer';
273  $nodelist = $this->doXPathQuery($query);
274 
275  if ($attr = $nodelist->item(0)) {
276  return $attr->value;
277  } else {
278  throw new \Exception('Could not find Issuer field in Authentication response');
279  }
280  }
281 
282  public function getNameID()
283  {
284  $nameID = array();
285 
286  $query = '/shibp:Response/shib:Assertion/shib:AuthenticationStatement/shib:Subject/shib:NameIdentifier';
287  $nodelist = $this->doXPathQuery($query);
288 
289  if ($node = $nodelist->item(0)) {
290  $nameID["Value"] = $node->nodeValue;
291  $nameID["Format"] = $node->getAttribute('Format');
292  }
293 
294  return $nameID;
295  }
296 
297 
308  {
309  assert('is_string($shire)');
310  assert('$attributes === NULL || is_array($attributes)');
311 
312  if ($sp->hasValue('scopedattributes')) {
313  $scopedAttributes = $sp->getArray('scopedattributes');
314  } elseif ($idp->hasValue('scopedattributes')) {
315  $scopedAttributes = $idp->getArray('scopedattributes');
316  } else {
317  $scopedAttributes = array();
318  }
319 
321 
322  $issueInstant = Time::generateTimestamp();
323 
324  // 30 seconds timeskew back in time to allow differing clocks
325  $notBefore = Time::generateTimestamp(time() - 30);
326 
327 
328  $assertionExpire = Time::generateTimestamp(time() + 60 * 5);# 5 minutes
329  $assertionid = Random::generateID();
330 
331  $spEntityId = $sp->getString('entityid');
332 
333  $audience = $sp->getString('audience', $spEntityId);
334  $base64 = $sp->getBoolean('base64attributes', false);
335 
336  $namequalifier = $sp->getString('NameQualifier', $spEntityId);
338  $subjectNode =
339  '<Subject>' .
340  '<NameIdentifier' .
341  ' Format="urn:mace:shibboleth:1.0:nameIdentifier"' .
342  ' NameQualifier="' . htmlspecialchars($namequalifier) . '"' .
343  '>' .
344  htmlspecialchars($nameid) .
345  '</NameIdentifier>' .
346  '<SubjectConfirmation>' .
347  '<ConfirmationMethod>' .
348  'urn:oasis:names:tc:SAML:1.0:cm:bearer' .
349  '</ConfirmationMethod>' .
350  '</SubjectConfirmation>' .
351  '</Subject>';
352 
353  $encodedattributes = '';
354 
355  if (is_array($attributes)) {
356  $encodedattributes .= '<AttributeStatement>';
357  $encodedattributes .= $subjectNode;
358 
359  foreach ($attributes as $name => $value) {
360  $encodedattributes .= $this->enc_attribute($name, $value, $base64, $scopedAttributes);
361  }
362 
363  $encodedattributes .= '</AttributeStatement>';
364  }
365 
366  /*
367  * The SAML 1.1 response message
368  */
369  $response = '<Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol"
370  xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
371  xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
372  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" IssueInstant="' . $issueInstant. '"
373  MajorVersion="1" MinorVersion="1"
374  Recipient="' . htmlspecialchars($shire) . '" ResponseID="' . $id . '">
375  <Status>
376  <StatusCode Value="samlp:Success" />
377  </Status>
378  <Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion"
379  AssertionID="' . $assertionid . '" IssueInstant="' . $issueInstant. '"
380  Issuer="' . htmlspecialchars($idp->getString('entityid')) . '" MajorVersion="1" MinorVersion="1">
381  <Conditions NotBefore="' . $notBefore. '" NotOnOrAfter="'. $assertionExpire . '">
382  <AudienceRestrictionCondition>
383  <Audience>' . htmlspecialchars($audience) . '</Audience>
384  </AudienceRestrictionCondition>
385  </Conditions>
386  <AuthenticationStatement AuthenticationInstant="' . $issueInstant. '"
387  AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:unspecified">' .
388  $subjectNode . '
389  </AuthenticationStatement>
390  ' . $encodedattributes . '
391  </Assertion>
392 </Response>';
393 
394  return $response;
395  }
396 
397 
407  private function enc_attribute($name, $values, $base64, $scopedAttributes)
408  {
409  assert('is_string($name)');
410  assert('is_array($values)');
411  assert('is_bool($base64)');
412  assert('is_array($scopedAttributes)');
413 
414  if (in_array($name, $scopedAttributes, true)) {
415  $scoped = true;
416  } else {
417  $scoped = false;
418  }
419 
420  $attr = '<Attribute AttributeName="' . htmlspecialchars($name) . '" AttributeNamespace="urn:mace:shibboleth:1.0:attributeNamespace:uri">';
421  foreach ($values as $value) {
422  $scopePart = '';
423  if ($scoped) {
424  $tmp = explode('@', $value, 2);
425  if (count($tmp) === 2) {
426  $value = $tmp[0];
427  $scopePart = ' Scope="' . htmlspecialchars($tmp[1]) . '"';
428  }
429  }
430 
431  if ($base64) {
432  $value = base64_encode($value);
433  }
434 
435  $attr .= '<AttributeValue' . $scopePart . '>' . htmlspecialchars($value) . '</AttributeValue>';
436  }
437  $attr .= '</Attribute>';
438 
439  return $attr;
440  }
441 
459  protected static function checkDateConditions($start = null, $end = null)
460  {
461  $currentTime = time();
462 
463  if (!empty($start)) {
464  $startTime = Utils::xsDateTimeToTimestamp($start);
465  // allow for a 10 minute difference in time
466  if (($startTime < 0) || (($startTime - 600) > $currentTime)) {
467  return false;
468  }
469  }
470  if (!empty($end)) {
472  if (($endTime < 0) || ($endTime <= $currentTime)) {
473  return false;
474  }
475  }
476  return true;
477  }
478 }
static generateID()
Generate a random identifier, ID_LENGTH bytes long.
Definition: Random.php:26
generate(\SimpleSAML_Configuration $idp, \SimpleSAML_Configuration $sp, $shire, $attributes)
Build a authentication response.
static getMetadataHandler()
This function retrieves the current instance of the metadata handler.
getArray($name, $default=self::REQUIRED_OPTION)
This function retrieves an array configuration option.
isNodeValidated($node)
Checks if the given node is validated by the signature on this response.
$spEntityId
hasValue($name)
Check whether a key in the configuration exists or not.
$end
Definition: saml1-acs.php:18
if(!array_key_exists('StateId', $_REQUEST)) $id
enc_attribute($name, $values, $base64, $scopedAttributes)
Format a shib13 attribute.
$attributes
setMessageValidated($messageValidated)
Set whether this message was validated externally.
$metadata['__DYNAMIC:1__']
static generateTimestamp($instant=null)
This function generates a timestamp on the form used by the SAML protocols.
Definition: Time.php:32
$xml
Definition: metadata.php:240
if($format !==null) $name
Definition: metadata.php:146
$nameid
Definition: status.php:36
$query
getBoolean($name, $default=self::REQUIRED_OPTION)
This function retrieves a boolean configuration option.
catch(Exception $e) if(!($request instanceof \SAML2\ArtifactResolve)) $issuer
Create styles array
The data for the language used.
static xsDateTimeToTimestamp($time)
This function converts a SAML2 timestamp on the form yyyy-mm-ddThh:mm:ss(.s+)?Z to a UNIX timestamp...
Definition: Utils.php:721
$idp
Definition: prp.php:13
getString($name, $default=self::REQUIRED_OPTION)
This function retrieves a string configuration option.
static checkDateConditions($start=null, $end=null)
Check if we are currently between the given date & time conditions.
static getCertPath($path)
Resolves a path that may be relative to the cert-directory.
Definition: Config.php:22
getSessionIndex()
Retrieve the session index of this response.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
doXPathQuery($query, $node=null)
This function runs an xPath query on this authentication response.
$response
$key
Definition: croninfo.php:18