ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
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
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 {
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}
$_COOKIE['client_id']
Definition: server.php:9
$_GET["client_id"]
An exception for terminatinating execution or to throw for unit testing.
static info($string)
Definition: Logger.php:201
static setCookie($name, $value, $params=null, $throw=true)
Set a cookie.
Definition: HTTP.php:1107
static checkURLAllowed($url, array $trustedSites=null)
Check if a URL is valid and is in our list of allowed URLs.
Definition: HTTP.php:322
static redirectTrustedURL($url, $parameters=array())
This function redirects to the specified URL without performing any security checks.
Definition: HTTP.php:962
static getInstance($instancename='simplesaml')
Get a configuration file by its instance name.
static getMetadataHandler()
This function retrieves the current instance of the metadata handler.
static getSessionFromRequest()
Retrieves the current session.
Definition: Session.php:243
getCookie($name)
Retrieve cookie with the given name.
Definition: IdPDisco.php:192
setPreviousIdP($idp)
Save the current IdP choice to a cookie.
Definition: IdPDisco.php:389
getIdPList()
Retrieve the list of IdPs which are stored in the metadata.
Definition: IdPDisco.php:459
handleRequest()
Handles a request to this discovery service.
Definition: IdPDisco.php:546
filterList($list)
Filter the list of IdPs.
Definition: IdPDisco.php:496
start()
Check if an IdP is set or if the request is passive, and redirect accordingly.
Definition: IdPDisco.php:512
getScopedIDPList()
Return the list of scoped idp.
Definition: IdPDisco.php:480
getSelectedIdP()
Retrieve the users choice of IdP.
Definition: IdPDisco.php:271
log($message)
Log a message.
Definition: IdPDisco.php:176
__construct(array $metadataSets, $instance)
Initializes this discovery service.
Definition: IdPDisco.php:116
validateIdP($idp)
Validates the given IdP entity id.
Definition: IdPDisco.php:238
getPreviousIdP()
Retrieve the previous IdP the user used.
Definition: IdPDisco.php:333
getFromCIDRhint()
Retrieve a recommended IdP based on the IP address of the client.
Definition: IdPDisco.php:344
setCookie($name, $value)
Save cookie with the given name and value.
Definition: IdPDisco.php:212
getTargetIdP()
Determine which IdP the user should go to, if any.
Definition: IdPDisco.php:423
saveIdP()
Determine whether the choice of IdP should be saved.
Definition: IdPDisco.php:403
getSavedIdP()
Retrieve the users saved choice of IdP.
Definition: IdPDisco.php:307
getRecommendedIdP()
Try to determine which IdP the user should most likely use.
Definition: IdPDisco.php:365
if($format !==null) $name
Definition: metadata.php:146
catch(Exception $e) $message
Attribute-related utility methods.
$idp
Definition: prp.php:13
if(isset($_REQUEST['delete'])) $list
Definition: registry.php:41
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
$params
Definition: disable.php:11