ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
IdPDisco.php
Go to the documentation of this file.
1 <?php
2 
3 
17 {
18 
24  protected $config;
25 
31  protected $instance;
32 
33 
39  protected $metadata;
40 
41 
47  protected $session;
48 
49 
55  protected $metadataSets;
56 
57 
63  protected $spEntityId;
64 
71  protected $isPassive;
72 
78  protected $setIdPentityID = null;
79 
80 
87  protected $returnIdParam;
88 
96  protected $scopedIDPList = array();
97 
103  protected $returnURL;
104 
105 
116  public function __construct(array $metadataSets, $instance)
117  {
118  assert(is_string($instance));
119 
120  // initialize standard classes
121  $this->config = SimpleSAML_Configuration::getInstance();
124  $this->instance = $instance;
125  $this->metadataSets = $metadataSets;
126 
127  $this->log('Accessing discovery service.');
128 
129  // standard discovery service parameters
130  if (!array_key_exists('entityID', $_GET)) {
131  throw new Exception('Missing parameter: entityID');
132  } else {
133  $this->spEntityId = $_GET['entityID'];
134  }
135 
136  if (!array_key_exists('returnIDParam', $_GET)) {
137  $this->returnIdParam = 'entityID';
138  } else {
139  $this->returnIdParam = $_GET['returnIDParam'];
140  }
141 
142  $this->log('returnIdParam initially set to ['.$this->returnIdParam.']');
143 
144  if (!array_key_exists('return', $_GET)) {
145  throw new Exception('Missing parameter: return');
146  } else {
147  $this->returnURL = \SimpleSAML\Utils\HTTP::checkURLAllowed($_GET['return']);
148  }
149 
150  $this->isPassive = false;
151  if (array_key_exists('isPassive', $_GET)) {
152  if ($_GET['isPassive'] === 'true') {
153  $this->isPassive = true;
154  }
155  }
156  $this->log('isPassive initially set to ['.($this->isPassive ? 'TRUE' : 'FALSE').']');
157 
158  if (array_key_exists('IdPentityID', $_GET)) {
159  $this->setIdPentityID = $_GET['IdPentityID'];
160  }
161 
162  if (array_key_exists('IDPList', $_REQUEST)) {
163  $this->scopedIDPList = $_REQUEST['IDPList'];
164  }
165  }
166 
167 
176  protected function log($message)
177  {
178  SimpleSAML\Logger::info('idpDisco.'.$this->instance.': '.$message);
179  }
180 
181 
192  protected function getCookie($name)
193  {
194  $prefixedName = 'idpdisco_'.$this->instance.'_'.$name;
195  if (array_key_exists($prefixedName, $_COOKIE)) {
196  return $_COOKIE[$prefixedName];
197  } else {
198  return null;
199  }
200  }
201 
202 
212  protected function setCookie($name, $value)
213  {
214  $prefixedName = 'idpdisco_'.$this->instance.'_'.$name;
215 
216  $params = array(
217  // we save the cookies for 90 days
218  'lifetime' => (60 * 60 * 24 * 90),
219  // the base path for cookies. This should be the installation directory for SimpleSAMLphp
220  'path' => $this->config->getBasePath(),
221  'httponly' => false,
222  );
223 
224  \SimpleSAML\Utils\HTTP::setCookie($prefixedName, $value, $params, false);
225  }
226 
227 
238  protected function validateIdP($idp)
239  {
240  if ($idp === null) {
241  return null;
242  }
243 
244  if (!$this->config->getBoolean('idpdisco.validate', true)) {
245  return $idp;
246  }
247 
248  foreach ($this->metadataSets as $metadataSet) {
249  try {
250  $this->metadata->getMetaData($idp, $metadataSet);
251  return $idp;
252  } catch (Exception $e) {
253  // continue
254  }
255  }
256 
257  $this->log('Unable to validate IdP entity id ['.$idp.'].');
258 
259  // the entity id wasn't valid
260  return null;
261  }
262 
263 
271  protected function getSelectedIdP()
272  {
273  /* Parameter set from the Extended IdP Metadata Discovery Service Protocol, indicating that the user prefers
274  * this IdP.
275  */
276  if (!empty($this->setIdPentityID)) {
277  return $this->validateIdP($this->setIdPentityID);
278  }
279 
280  // user has clicked on a link, or selected the IdP from a drop-down list
281  if (array_key_exists('idpentityid', $_GET)) {
282  return $this->validateIdP($_GET['idpentityid']);
283  }
284 
285  /* Search for the IdP selection from the form used by the links view. This form uses a name which equals
286  * idp_<entityid>, so we search for that.
287  *
288  * Unfortunately, php replaces periods in the name with underscores, and there is no reliable way to get them
289  * back. Therefore we do some quick and dirty parsing of the query string.
290  */
291  $qstr = $_SERVER['QUERY_STRING'];
292  $matches = array();
293  if (preg_match('/(?:^|&)idp_([^=]+)=/', $qstr, $matches)) {
294  return $this->validateIdP(urldecode($matches[1]));
295  }
296 
297  // no IdP chosen
298  return null;
299  }
300 
301 
307  protected function getSavedIdP()
308  {
309  if (!$this->config->getBoolean('idpdisco.enableremember', false)) {
310  // saving of IdP choices is disabled
311  return null;
312  }
313 
314  if ($this->getCookie('remember') === '1') {
315  $this->log('Return previously saved IdP because of remember cookie set to 1');
316  return $this->getPreviousIdP();
317  }
318 
319  if ($this->isPassive) {
320  $this->log('Return previously saved IdP because of isPassive');
321  return $this->getPreviousIdP();
322  }
323 
324  return null;
325  }
326 
327 
333  protected function getPreviousIdP()
334  {
335  return $this->validateIdP($this->getCookie('lastidp'));
336  }
337 
338 
344  protected function getFromCIDRhint()
345  {
346  foreach ($this->metadataSets as $metadataSet) {
347  $idp = $this->metadata->getPreferredEntityIdFromCIDRhint($metadataSet, $_SERVER['REMOTE_ADDR']);
348  if (!empty($idp)) {
349  return $idp;
350  }
351  }
352 
353  return null;
354  }
355 
356 
365  protected function getRecommendedIdP()
366  {
367  $idp = $this->getPreviousIdP();
368  if ($idp !== null) {
369  $this->log('Preferred IdP from previous use ['.$idp.'].');
370  return $idp;
371  }
372 
373  $idp = $this->getFromCIDRhint();
374 
375  if (!empty($idp)) {
376  $this->log('Preferred IdP from CIDR hint ['.$idp.'].');
377  return $idp;
378  }
379 
380  return null;
381  }
382 
383 
389  protected function setPreviousIdP($idp)
390  {
391  assert(is_string($idp));
392 
393  $this->log('Choice made ['.$idp.'] Setting cookie.');
394  $this->setCookie('lastidp', $idp);
395  }
396 
397 
403  protected function saveIdP()
404  {
405  if (!$this->config->getBoolean('idpdisco.enableremember', false)) {
406  // saving of IdP choices is disabled
407  return false;
408  }
409 
410  if (array_key_exists('remember', $_GET)) {
411  return true;
412  }
413 
414  return false;
415  }
416 
417 
423  protected function getTargetIdP()
424  {
425  // first, check if the user has chosen an IdP
426  $idp = $this->getSelectedIdP();
427  if ($idp !== null) {
428  // the user selected this IdP. Save the choice in a cookie
429  $this->setPreviousIdP($idp);
430 
431  if ($this->saveIdP()) {
432  $this->setCookie('remember', '1');
433  } else {
434  $this->setCookie('remember', '0');
435  }
436 
437  return $idp;
438  }
439 
440  $this->log('getSelectedIdP() returned null');
441 
442  // check if the user has saved an choice earlier
443  $idp = $this->getSavedIdP();
444  if ($idp !== null) {
445  $this->log('Using saved choice ['.$idp.'].');
446  return $idp;
447  }
448 
449  // the user has made no choice
450  return null;
451  }
452 
453 
459  protected function getIdPList()
460  {
461  $idpList = array();
462  foreach ($this->metadataSets as $metadataSet) {
463  $newList = $this->metadata->getList($metadataSet);
464  /*
465  * Note that we merge the entities in reverse order. This ensures that it is the entity in the first
466  * metadata set that "wins" if two metadata sets have the same entity.
467  */
468  $idpList = array_merge($newList, $idpList);
469  }
470 
471  return $idpList;
472  }
473 
474 
480  protected function getScopedIDPList()
481  {
482  return $this->scopedIDPList;
483  }
484 
485 
496  protected function filterList($list)
497  {
498  foreach ($list as $entity => $metadata) {
499  if (array_key_exists('hide.from.discovery', $metadata) && $metadata['hide.from.discovery'] === true) {
500  unset($list[$entity]);
501  }
502  }
503  return $list;
504  }
505 
506 
512  protected function start()
513  {
514  $idp = $this->getTargetIdp();
515  if ($idp !== null) {
516  $extDiscoveryStorage = $this->config->getString('idpdisco.extDiscoveryStorage', null);
517  if ($extDiscoveryStorage !== null) {
518  $this->log('Choice made ['.$idp.'] (Forwarding to external discovery storage)');
519  \SimpleSAML\Utils\HTTP::redirectTrustedURL($extDiscoveryStorage, array(
520  'entityID' => $this->spEntityId,
521  'IdPentityID' => $idp,
522  'returnIDParam' => $this->returnIdParam,
523  'isPassive' => 'true',
524  'return' => $this->returnURL
525  ));
526  } else {
527  $this->log(
528  'Choice made ['.$idp.'] (Redirecting the user back. returnIDParam='.$this->returnIdParam.')'
529  );
530  \SimpleSAML\Utils\HTTP::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idp));
531  }
532  }
533 
534  if ($this->isPassive) {
535  $this->log('Choice not made. (Redirecting the user back without answer)');
537  }
538  }
539 
540 
546  public function handleRequest()
547  {
548  $this->start();
549 
550  // no choice made. Show discovery service page
551  $idpList = $this->getIdPList();
552  $idpList = $this->filterList($idpList);
553  $preferredIdP = $this->getRecommendedIdP();
554 
555  $idpintersection = array_intersect(array_keys($idpList), $this->getScopedIDPList());
556  if (sizeof($idpintersection) > 0) {
557  $idpList = array_intersect_key($idpList, array_fill_keys($idpintersection, null));
558  }
559 
560  $idpintersection = array_values($idpintersection);
561 
562  if (sizeof($idpintersection) == 1) {
563  $this->log(
564  'Choice made ['.$idpintersection[0].'] (Redirecting the user back. returnIDParam='.
565  $this->returnIdParam.')'
566  );
568  $this->returnURL,
569  array($this->returnIdParam => $idpintersection[0])
570  );
571  }
572 
573  /*
574  * Make use of an XHTML template to present the select IdP choice to the user. Currently the supported options
575  * is either a drop down menu or a list view.
576  */
577  switch ($this->config->getString('idpdisco.layout', 'links')) {
578  case 'dropdown':
579  $templateFile = 'selectidp-dropdown.php';
580  break;
581  case 'links':
582  $templateFile = 'selectidp-links.php';
583  break;
584  default:
585  throw new Exception('Invalid value for the \'idpdisco.layout\' option.');
586  }
587 
588  $t = new SimpleSAML_XHTML_Template($this->config, $templateFile, 'disco');
589  $t->data['idplist'] = $idpList;
590  $t->data['preferredidp'] = $preferredIdP;
591  $t->data['return'] = $this->returnURL;
592  $t->data['returnIDParam'] = $this->returnIdParam;
593  $t->data['entityID'] = $this->spEntityId;
594  $t->data['urlpattern'] = htmlspecialchars(\SimpleSAML\Utils\HTTP::getSelfURLNoQuery());
595  $t->data['rememberenabled'] = $this->config->getBoolean('idpdisco.enableremember', false);
596  $t->show();
597  }
598 }
getFromCIDRhint()
Retrieve a recommended IdP based on the IP address of the client.
Definition: IdPDisco.php:344
$_COOKIE['client_id']
Definition: server.php:9
static getMetadataHandler()
This function retrieves the current instance of the metadata handler.
if(isset($_REQUEST['delete'])) $list
Definition: registry.php:41
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
static checkURLAllowed($url, array $trustedSites=null)
Check if a URL is valid and is in our list of allowed URLs.
Definition: HTTP.php:321
start()
Check if an IdP is set or if the request is passive, and redirect accordingly.
Definition: IdPDisco.php:512
$_GET["client_id"]
__construct(array $metadataSets, $instance)
Initializes this discovery service.
Definition: IdPDisco.php:116
static redirectTrustedURL($url, $parameters=array())
This function redirects to the specified URL without performing any security checks.
Definition: HTTP.php:959
setPreviousIdP($idp)
Save the current IdP choice to a cookie.
Definition: IdPDisco.php:389
Attribute-related utility methods.
static info($string)
Definition: Logger.php:199
catch(Exception $e) $message
static setCookie($name, $value, $params=null, $throw=true)
Set a cookie.
Definition: HTTP.php:1104
getCookie($name)
Retrieve cookie with the given name.
Definition: IdPDisco.php:192
getScopedIDPList()
Return the list of scoped idp.
Definition: IdPDisco.php:480
getIdPList()
Retrieve the list of IdPs which are stored in the metadata.
Definition: IdPDisco.php:459
setCookie($name, $value)
Save cookie with the given name and value.
Definition: IdPDisco.php:212
log($message)
Log a message.
Definition: IdPDisco.php:176
instance(Loop $newLoop=null)
Retrieves or sets the global Loop object.
Definition: functions.php:173
getTargetIdP()
Determine which IdP the user should go to, if any.
Definition: IdPDisco.php:423
getSelectedIdP()
Retrieve the users choice of IdP.
Definition: IdPDisco.php:271
getSavedIdP()
Retrieve the users saved choice of IdP.
Definition: IdPDisco.php:307
handleRequest()
Handles a request to this discovery service.
Definition: IdPDisco.php:546
getRecommendedIdP()
Try to determine which IdP the user should most likely use.
Definition: IdPDisco.php:365
getPreviousIdP()
Retrieve the previous IdP the user used.
Definition: IdPDisco.php:333
$idp
Definition: prp.php:13
saveIdP()
Determine whether the choice of IdP should be saved.
Definition: IdPDisco.php:403
static getSessionFromRequest()
Retrieves the current session.
Definition: Session.php:241
validateIdP($idp)
Validates the given IdP entity id.
Definition: IdPDisco.php:238
static getInstance($instancename='simplesaml')
Get a configuration file by its instance name.
filterList($list)
Filter the list of IdPs.
Definition: IdPDisco.php:496