diff --git a/config-templates/config.php b/config-templates/config.php index 50f2f650bc32a51d13b4c1443c0d29f399d1e437..3b6399696a3152b8fa8ddf1c6839db795ee0475d 100644 --- a/config-templates/config.php +++ b/config-templates/config.php @@ -182,7 +182,12 @@ $config = array ( */ 'idpdisco.url.shib13' => NULL, 'idpdisco.url.saml20' => NULL, - + + /* + * Whether the discovery service should allow the user to save his choice of IdP. + */ + 'idpdisco.enableremember' => TRUE, + /* * IdP Discovery service look configuration. * Wether to display a list of idp or to display a dropdown box. For many IdP' a dropdown box diff --git a/templates/default/selectidp-dropdown.php b/templates/default/selectidp-dropdown.php index 46ea384f89c6b36e07bf0783a4f08995df70114f..d2e324985def7cd5d8b2e1b6f6811597fe17eb59 100644 --- a/templates/default/selectidp-dropdown.php +++ b/templates/default/selectidp-dropdown.php @@ -5,7 +5,7 @@ if(!array_key_exists('header', $this->data)) { } $this->data['header'] = $this->t($this->data['header']); -$this->data['autofocus'] = 'selectbutton'; +$this->data['autofocus'] = 'dropdownlist'; $this->includeAtTemplateBase('includes/header.php'); @@ -28,7 +28,7 @@ foreach ($this->data['idplist'] AS $idpentry) { <input type="hidden" name="entityID" value="<?php echo htmlspecialchars($this->data['entityID']); ?>" /> <input type="hidden" name="return" value="<?php echo htmlspecialchars($this->data['return']); ?>" /> <input type="hidden" name="returnIDParam" value="<?php echo htmlspecialchars($this->data['returnIDParam']); ?>" /> - <select name="idpentityid"> + <select id="dropdownlist" name="idpentityid"> <?php foreach ($this->data['idplist'] AS $idpentry) { @@ -43,7 +43,12 @@ foreach ($this->data['idplist'] AS $idpentry) { } ?> </select> - <input id="selectbutton" type="submit" value="<?php echo $this->t('select'); ?>"/> + <input type="submit" value="<?php echo $this->t('select'); ?>"/> + <?php + if($this->data['rememberenabled']) { + echo('<br/><input type="checkbox" name="remember" value="1" />' . $this->t('remember')); + } + ?> </form> diff --git a/templates/default/selectidp-links.php b/templates/default/selectidp-links.php index debc78d2fbef443dfa0a99939d47904059e82dde..e5e27dfb90e5f35718a5386977bd5fb3660b038b 100644 --- a/templates/default/selectidp-links.php +++ b/templates/default/selectidp-links.php @@ -21,10 +21,19 @@ foreach ($this->data['idplist'] AS $idpentry) { <div id="content"> <h2><?php echo $this->data['header']; ?></h2> + + <form method="get" action="<?php echo $this->data['urlpattern']; ?>"> + <input type="hidden" name="entityID" value="<?php echo htmlspecialchars($this->data['entityID']); ?>" /> + <input type="hidden" name="return" value="<?php echo htmlspecialchars($this->data['return']); ?>" /> + <input type="hidden" name="returnIDParam" value="<?php echo htmlspecialchars($this->data['returnIDParam']); ?>" /> - <p><?php echo $this->t('selectidp_full'); ?></p> - - + <p><?php + echo $this->t('selectidp_full'); + if($this->data['rememberenabled']) { + echo('<br /><input type="checkbox" name="remember" value="1" />' . $this->t('remember')); + } + ?></p> + <?php @@ -41,7 +50,9 @@ foreach ($this->data['idplist'] AS $idpentry) { echo htmlspecialchars($this->t('idpname_' . $idpentry['entityid'])) . '</h3>'; echo ' <p>' . htmlspecialchars($this->t('idpdesc_' . $idpentry['entityid'])) . '<br />'; - echo ' [ <a id="preferredidp" href="' . $this->data['urlpattern'] . htmlspecialchars($idpentry['entityid']) . '">Select this IdP</a>]</p>'; + echo('<input id="preferredidp" type="submit" name="idp_' . + htmlspecialchars($idpentry['entityid']) . '" value="' . + $this->t('select') . '" /></p>'); echo '</div>'; } @@ -56,11 +67,13 @@ foreach ($this->data['idplist'] AS $idpentry) { echo htmlspecialchars($this->t('idpname_' . $idpentry['entityid'])) . '</h3>'; echo ' <p>' . htmlspecialchars($this->t('idpdesc_' . $idpentry['entityid'])) . '<br />'; - echo '[ <a href="' . $this->data['urlpattern'] . htmlspecialchars($idpentry['entityid']) . '">Select this IdP</a>]</p>'; + echo('<input id="preferredidp" type="submit" name="idp_' . + htmlspecialchars($idpentry['entityid']) . '" value="' . + $this->t('select') . '" /></p>'); } } ?> - + </form> <?php $this->includeAtTemplateBase('includes/footer.php'); ?> diff --git a/www/cleardiscochoices.php b/www/cleardiscochoices.php new file mode 100644 index 0000000000000000000000000000000000000000..02f3042f2a2974d60a31d60945f822c8f26badb3 --- /dev/null +++ b/www/cleardiscochoices.php @@ -0,0 +1,48 @@ +<?php + +require_once('_include.php'); + +/** + * This page clears the user's IdP discovery choices. + */ + +/* The base path for cookies. This should be the installation directory for simpleSAMLphp. */ +$config = SimpleSAML_Configuration::getInstance(); +$cookiePath = '/' . $config->getBaseUrl(); + +/* List over the cookies we should delete. */ +$deleteCookies = array( + 'idpdisco_saml20_rememberchoice', + 'idpdisco_shib13_rememberchoice', + ); + +error_log(var_export($_COOKIE, TRUE)); + +/* Delete the cookies. */ +foreach($deleteCookies as $cookieName) { + if(!array_key_exists($cookieName, $_COOKIE)) { + /* Cookie doesn't exist. */ + continue; + } + + error_log('Deleting: ' . $cookieName); + + /* Delete the cookie. We delete it once without the secure flag and once with the secure flag. This + * ensures that the cookie will be deleted in any case. + */ + setcookie($cookieName, '', time() - 24*60*60, $cookiePath); +} + + +/* Find where we should go now. */ +if(array_key_exists('ReturnTo', $_REQUEST)) { + $returnTo = $_REQUEST['ReturnTo']; +} else { + /* Return to the front page if no other destination is given. This is the same as the base cookie path. */ + $returnTo = $cookiePath; +} + +/* Redirect to destination. */ +SimpleSAML_Utilities::redirect($returnTo); + +?> \ No newline at end of file diff --git a/www/index.php b/www/index.php index 863a6cbe28ca31fa53852400fc4be00f20520b57..242715de871db9663664d00f132cc5a2db5e923c 100644 --- a/www/index.php +++ b/www/index.php @@ -53,6 +53,12 @@ $links[] = array( 'text' => 'link_configcheck', ); +if($config->getBoolean('idpdisco.enableremember', FALSE)) { + $links[] = array( + 'href' => 'cleardiscochoices.php', + 'text' => 'link_cleardiscochoices', + ); +} $linksmeta = array(); diff --git a/www/saml2/sp/idpdisco.php b/www/saml2/sp/idpdisco.php index add6a3620c225b2f68b121c171b9b6c431cf5cb0..93fa7fd9e52149a95df0a14490cc4ab2ed99f796 100644 --- a/www/saml2/sp/idpdisco.php +++ b/www/saml2/sp/idpdisco.php @@ -4,15 +4,22 @@ require_once('../../_include.php'); $config = SimpleSAML_Configuration::getInstance(); $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); - $session = SimpleSAML_Session::getInstance(); + SimpleSAML_Logger::info('SAML2.0 - SP.idpDisco: Accessing SAML 2.0 discovery service'); -if (!$config->getValue('enable.saml20-sp', false)) +if (!$config->getValue('enable.saml20-sp', false)) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NOACCESS'); +} - +/* The base path for cookies. This should be the installation directory for simpleSAMLphp. */ +$cookiePath = '/' . $config->getBaseUrl(); + +/* Has the admin enabled the remember choice option in the config? */ +$rememberEnabled = $config->getBoolean('idpdisco.enableremember', FALSE); + +/* Check script parameters. */ try { if (!isset($_GET['entityID'])) throw new Exception('Missing parameter: entityID'); @@ -28,55 +35,124 @@ try { } -if (isset($_GET['idpentityid'])) { +$selectedIdP = NULL; +$userSelectedIdP = FALSE; - SimpleSAML_Logger::info('SAML2.0 - SP.idpDisco: Choice made [ ' . $_GET['idpentityid'] . '] Setting preferedidp cookie.'); +/* Check for dropdown-style of IdP selection. */ +if(array_key_exists('idpentityid', $_GET)) { + $selectedIdP = $_GET['idpentityid']; + $userSelectedIdP = TRUE; - $idpentityid = $_GET['idpentityid']; - setcookie('preferedidp',$idpentityid,time()+60*60*24*90); // set cookie valid 90 days - - $returnurl = SimpleSAML_Utilities::addURLparameter($return, $returnidparam . '=' . $idpentityid); - SimpleSAML_Utilities::redirect($returnurl); - } +if($selectedIdP === NULL) { + /* Search for the IdP selection from the form used by the links view. + * This form uses a name which equals idp_<entityid>, so we search for that. + * + * Unfortunately, php replaces periods in the name with underscores, and there + * is no reliable way to get them back. Therefore we do some quick and dirty + * parsing of the query string. + */ + $qstr = $_SERVER['QUERY_STRING']; + $matches = array(); + if(preg_match('/(?:^|&)idp_([^=]+)=/', $qstr, $matches)) { + $selectedIdP = urldecode($matches[1]); + $userSelectedIdP = TRUE; + } +} + + +if($selectedIdP === NULL && $rememberEnabled) { + /* No choice made by the user. Check if there is a remembered IdP for the user. */ + + if(array_key_exists('idpdisco_saml20_rememberchoice', $_COOKIE) && + array_key_exists('idpdisco_saml20_lastidp', $_COOKIE)) { + + $selectedIdP = $_COOKIE['idpdisco_saml20_lastidp']; + $userSelectedIdP = FALSE; + } +} + +/* Check that the selected IdP is a valid IdP. */ +if($selectedIdP !== NULL) { + try { + $idpMetadata = $metadata->getMetaData($selectedIdP, 'saml20-idp-remote'); + } catch(Exception $e) { + /* The entity id wasn't valid. */ + $selectedIdP = NULL; + $userSelectedIdP = FALSE; + } +} + +if($selectedIdP !== NULL) { + /* We have an IdP selection. */ + + if($userSelectedIdP) { + /* We save the users choice for 90 days. */ + $saveUntil = time() + 60*60*24*90; + + SimpleSAML_Logger::info('SAML2.0 - SP.idpDisco: Choice made [ ' . $selectedIdP . ']' . + ' Setting idpdisco_saml20_lastidp cookie.'); + setcookie('idpdisco_saml20_lastidp', $selectedIdP, $saveUntil, $cookiePath); + + if($rememberEnabled) { + if(array_key_exists('remember', $_GET)) { + /* The remember choice option is enabled, and the user has selected + * "remember choice" in the IdP list. Save this choice. + */ + setcookie('idpdisco_saml20_rememberchoice', 1, $saveUntil, $cookiePath); + } + } + } + + SimpleSAML_Utilities::redirect($return, array($returnidparam => $selectedIdP)); +} + + +/* Load list of entities. */ try { $idplist = $metadata->getList('saml20-idp-remote'); $preferredidp = $metadata->getPreferredEntityIdFromCIDRhint('saml20-idp-remote', $_SERVER['REMOTE_ADDR']); - if (!empty($preferredidp)) + if (!empty($preferredidp)) { SimpleSAML_Logger::info('SAML2.0 - SP.idpDisco: Preferred IdP from CIDR hint [ ' . $preferredidp . '].'); - -} catch (Exception $exception) { + } +} catch(Exception $exception) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception); } -if (!empty($_COOKIE['preferedidp'])) { - $preferredidp = $_COOKIE['preferedidp']; +if(array_key_exists('idpdisco_saml20_lastidp', $_COOKIE)) { + $preferredidp = $_COOKIE['idpdisco_saml20_lastidp']; SimpleSAML_Logger::info('SAML2.0 - SP.idpDisco: Preferred IdP overridden from cookie [ ' . $preferredidp . '].'); } -/** +/* * Make use of an XHTML template to present the select IdP choice to the user. * Currently the supported options is either a drop down menu or a list view. */ -$templatefile = ($config->getValue('idpdisco.layout') == 'dropdown' ? 'selectidp-dropdown.php' : 'selectidp-links.php'); +switch($config->getString('idpdisco.layout', 'links')) { + case 'dropdown': + $templatefile = 'selectidp-dropdown.php'; + break; + case 'links': + $templatefile = 'selectidp-links.php'; + break; + default: + throw new Exception('Invalid value for the \'idpdisco.layout\' option.'); +} + $t = new SimpleSAML_XHTML_Template($config, $templatefile, 'disco.php'); $t->data['idplist'] = $idplist; $t->data['preferredidp'] = $preferredidp; -if ($config->getValue('idpdisco.layout') == 'dropdown') { - $t->data['return']= $return; - $t->data['returnIDParam'] = $returnidparam; - $t->data['entityID'] = $spentityid; - $t->data['urlpattern'] = htmlspecialchars(SimpleSAML_Utilities::selfURLNoQuery()); - $t->show(); +$t->data['return']= $return; +$t->data['returnIDParam'] = $returnidparam; +$t->data['entityID'] = $spentityid; +$t->data['urlpattern'] = htmlspecialchars(SimpleSAML_Utilities::selfURLNoQuery()); -} else { - $t->data['urlpattern'] = htmlspecialchars(SimpleSAML_Utilities::selfURL() . '&idpentityid='); - $t->show(); -} +$t->data['rememberenabled'] = $rememberEnabled; +$t->show(); ?> \ No newline at end of file diff --git a/www/shib13/sp/idpdisco.php b/www/shib13/sp/idpdisco.php index 6f7e5c3c0e9968d02620c1cc754e90f00bb7cf7c..3044a6c9735bd795f749df4cdc7c293b6163f411 100644 --- a/www/shib13/sp/idpdisco.php +++ b/www/shib13/sp/idpdisco.php @@ -4,16 +4,22 @@ require_once('../../_include.php'); $config = SimpleSAML_Configuration::getInstance(); $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); - - $session = SimpleSAML_Session::getInstance(); -SimpleSAML_Logger::info('Shib1.3 - SP.idpDisco : Accessing Shib 1.3 discovery service'); -if (!$config->getValue('enable.shib13-sp', false)) +SimpleSAML_Logger::info('Shib1.3 - SP.idpDisco: Accessing Shib 1.3 discovery service'); + +if (!$config->getValue('enable.shib13-sp', false)) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'NOACCESS'); +} +/* The base path for cookies. This should be the installation directory for simpleSAMLphp. */ +$cookiePath = '/' . $config->getBaseUrl(); +/* Has the admin enabled the remember choice option in the config? */ +$rememberEnabled = $config->getBoolean('idpdisco.enableremember', FALSE); + +/* Check script parameters. */ try { if (!isset($_GET['entityID'])) throw new Exception('Missing parameter: entityID'); @@ -29,41 +35,124 @@ try { } -if (isset($_GET['idpentityid'])) { +$selectedIdP = NULL; +$userSelectedIdP = FALSE; - SimpleSAML_Logger::info('Shib1.3 - SP.idpDisco : Choice made [ ' . $_GET['idpentityid'] . '] Setting preferedidp cookie.'); +/* Check for dropdown-style of IdP selection. */ +if(array_key_exists('idpentityid', $_GET)) { + $selectedIdP = $_GET['idpentityid']; + $userSelectedIdP = TRUE; - $idpentityid = $_GET['idpentityid']; - setcookie('preferedidp',$idpentityid,time()+60*60*24*90); // set cookie valid 90 days - - $returnurl = SimpleSAML_Utilities::addURLparameter($return, $returnidparam . '=' . $idpentityid); - SimpleSAML_Utilities::redirect($returnurl); - } +if($selectedIdP === NULL) { + /* Search for the IdP selection from the form used by the links view. + * This form uses a name which equals idp_<entityid>, so we search for that. + * + * Unfortunately, php replaces periods in the name with underscores, and there + * is no reliable way to get them back. Therefore we do some quick and dirty + * parsing of the query string. + */ + $qstr = $_SERVER['QUERY_STRING']; + $matches = array(); + if(preg_match('/(?:^|&)idp_([^=]+)=/', $qstr, $matches)) { + $selectedIdP = urldecode($matches[1]); + $userSelectedIdP = TRUE; + } +} + + +if($selectedIdP === NULL && $rememberEnabled) { + /* No choice made by the user. Check if there is a remembered IdP for the user. */ + + if(array_key_exists('idpdisco_shib13_rememberchoice', $_COOKIE) && + array_key_exists('idpdisco_shib13_lastidp', $_COOKIE)) { + + $selectedIdP = $_COOKIE['idpdisco_shib13_lastidp']; + $userSelectedIdP = FALSE; + } +} + +/* Check that the selected IdP is a valid IdP. */ +if($selectedIdP !== NULL) { + try { + $idpMetadata = $metadata->getMetaData($selectedIdP, 'shib13-idp-remote'); + } catch(Exception $e) { + /* The entity id wasn't valid. */ + $selectedIdP = NULL; + $userSelectedIdP = FALSE; + } +} + +if($selectedIdP !== NULL) { + /* We have an IdP selection. */ + + if($userSelectedIdP) { + /* We save the users choice for 90 days. */ + $saveUntil = time() + 60*60*24*90; + + SimpleSAML_Logger::info('Shib1.3 - SP.idpDisco: Choice made [ ' . $selectedIdP . ']' . + ' Setting idpdisco_shib13_lastidp cookie.'); + setcookie('idpdisco_shib13_lastidp', $selectedIdP, $saveUntil, $cookiePath); + + if($rememberEnabled) { + if(array_key_exists('remember', $_GET)) { + /* The remember choice option is enabled, and the user has selected + * "remember choice" in the IdP list. Save this choice. + */ + setcookie('idpdisco_shib13_rememberchoice', 1, $saveUntil, $cookiePath); + } + } + } + + SimpleSAML_Utilities::redirect($return, array($returnidparam => $selectedIdP)); +} + + +/* Load list of entities. */ try { $idplist = $metadata->getList('shib13-idp-remote'); -} catch (Exception $exception) { + $preferredidp = $metadata->getPreferredEntityIdFromCIDRhint('shib13-idp-remote', $_SERVER['REMOTE_ADDR']); + + if (!empty($preferredidp)) { + SimpleSAML_Logger::info('Shib1.3 - SP.idpDisco: Preferred IdP from CIDR hint [ ' . $preferredidp . '].'); + } +} catch(Exception $exception) { SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception); } -if ($config->getValue('idpdisco.layout') == 'dropdown') { - $t = new SimpleSAML_XHTML_Template($config, 'selectidp-dropdown.php', 'disco.php'); - $t->data['idplist'] = $idplist; - $t->data['return']= $return; - $t->data['returnIDParam'] = $returnidparam; - $t->data['entityID'] = $spentityid; - $t->data['preferedidp'] = (!empty($_COOKIE['preferedidp'])) ? $_COOKIE['preferedidp'] : null; - $t->data['urlpattern'] = htmlspecialchars(SimpleSAML_Utilities::selfURLNoQuery()); - $t->show(); +if(array_key_exists('idpdisco_shib13_lastidp', $_COOKIE)) { + $preferredidp = $_COOKIE['idpdisco_shib13_lastidp']; + SimpleSAML_Logger::info('Shib1.3 - SP.idpDisco: Preferred IdP overridden from cookie [ ' . $preferredidp . '].'); } -else -{ - $t = new SimpleSAML_XHTML_Template($config, 'selectidp-links.php', 'disco.php'); - $t->data['idplist'] = $idplist; - $t->data['urlpattern'] = htmlspecialchars(SimpleSAML_Utilities::selfURL() . '&idpentityid='); - $t->show(); + + +/* + * Make use of an XHTML template to present the select IdP choice to the user. + * Currently the supported options is either a drop down menu or a list view. + */ +switch($config->getString('idpdisco.layout', 'links')) { + case 'dropdown': + $templatefile = 'selectidp-dropdown.php'; + break; + case 'links': + $templatefile = 'selectidp-links.php'; + break; + default: + throw new Exception('Invalid value for the \'idpdisco.layout\' option.'); } +$t = new SimpleSAML_XHTML_Template($config, $templatefile, 'disco.php'); +$t->data['idplist'] = $idplist; +$t->data['preferredidp'] = $preferredidp; + +$t->data['return']= $return; +$t->data['returnIDParam'] = $returnidparam; +$t->data['entityID'] = $spentityid; +$t->data['urlpattern'] = htmlspecialchars(SimpleSAML_Utilities::selfURLNoQuery()); + +$t->data['rememberenabled'] = $rememberEnabled; + +$t->show(); ?> \ No newline at end of file