, UNINETT AS. * @package simpleSAMLphp */ class sspmod_discopower_PowerIdPDisco extends SimpleSAML_XHTML_IdPDisco { private $discoconfig; /** * The domain to use when saving common domain cookies. * This is NULL if support for common domain cookies is disabled. * * @var string|NULL */ private $cdcDomain; /** * The lifetime of the CDC cookie, in seconds. * If set to NULL, it will only be valid until the browser is closed. * * @var int|NULL */ private $cdcLifetime; /** * Initializes this discovery service. * * The constructor does the parsing of the request. If this is an invalid request, it will * throw an exception. * * @param array $metadataSets Array with metadata sets we find remote entities in. * @param string $instance The name of this instance of the discovery service. */ public function __construct(array $metadataSets, $instance) { parent::__construct($metadataSets, $instance); $this->discoconfig = SimpleSAML_Configuration::getConfig('module_discopower.php'); $this->cdcDomain = $this->discoconfig->getString('cdc.domain', NULL); if ($this->cdcDomain !== NULL && $this->cdcDomain[0] !== '.') { /* Ensure that the CDC domain starts with a dot ('.') as required by the spec. */ $this->cdcDomain = '.' . $this->cdcDomain; } $this->cdcLifetime = $this->discoconfig->getInteger('cdc.lifetime', NULL); } /** * Log a message. * * This is an helper function for logging messages. It will prefix the messages with our * discovery service type. * * @param $message The message which should be logged. */ protected function log($message) { SimpleSAML_Logger::info('PowerIdPDisco.' . $this->instance . ': ' . $message); } /** * Compare two entities. * * This function is used to sort the entity list. It sorts based on english name, * and will always put IdP's with names configured before those with only an * entityID. * * @param array $a The metadata of the first entity. * @param array $b The metadata of the second entity. * @return int How $a compares to $b. */ public static function mcmp(array $a, array $b) { if (isset($a['name']['en']) && isset($b['name']['en'])) { return strcasecmp($a['name']['en'], $b['name']['en']); } elseif (isset($a['name']['en'])) { return -1; /* Place name before entity ID. */ } elseif (isset($b['name']['en'])) { return 1; /* Place entity ID after name. */ } else { return strcasecmp($a['entityid'], $b['entityid']); } } /* * This function will structure the idp list in a hierarchy based upon the tags. */ protected function idplistStructured($list) { # echo '
'; print_r($list); exit;
$slist = array();
$order = $this->discoconfig->getValue('taborder');
if (is_array($order)) {
foreach($order AS $oe) {
$slist[$oe] = array();
}
}
$enableTabs = $this->discoconfig->getValue('tabs', NULL);
foreach($list AS $key => $val) {
$tags = array('misc');
if (array_key_exists('tags', $val)) {
$tags = $val['tags'];
}
foreach ($tags AS $tag) {
if (!empty($enableTabs) && !in_array($tag, $enableTabs)) continue;
$slist[$tag][$key] = $val;
}
}
foreach($slist AS $tab => $tbslist) {
uasort($slist[$tab], array('sspmod_discopower_PowerIdPDisco', 'mcmp'));
}
return $slist;
}
private function processFilter($filter, $entry, $default = TRUE) {
if (in_array($entry['entityid'], $filter['entities.include'] )) return TRUE;
if (in_array($entry['entityid'], $filter['entities.exclude'] )) return FALSE;
if (array_key_exists('tags', $entry)) {
foreach ($filter['tags.include'] AS $fe) {
if (in_array($fe, $entry['tags'])) return TRUE;
}
foreach ($filter['tags.exclude'] AS $fe) {
if (in_array($fe, $entry['tags'])) return FALSE;
}
}
return $default;
}
protected function filterList($list) {
try {
$spmd = $this->metadata->getMetaData($this->spEntityId, 'saml20-sp-remote');
} catch(Exception $e) {
return $list;
}
if (!isset($spmd)) return $list;
if (!array_key_exists('discopower.filter', $spmd)) return $list;
$filter = $spmd['discopower.filter'];
if (!array_key_exists('entities.include', $filter)) $filter['entities.include'] = array();
if (!array_key_exists('entities.exclude', $filter)) $filter['entities.exclude'] = array();
if (!array_key_exists('tags.include', $filter)) $filter['tags.include'] = array();
if (!array_key_exists('tags.exclude', $filter)) $filter['tags.exclude'] = array();
$defaultrule = TRUE;
if ( array_key_exists('entities.include', $spmd['discopower.filter'] ) ||
array_key_exists('tags.include', $spmd['discopower.filter'])) {
$defaultrule = FALSE;
}
$returnlist = array();
foreach ($list AS $key => $entry) {
if ($this->processFilter($filter, $entry, $defaultrule)) {
$returnlist[$key] = $entry;
}
}
return $returnlist;
}
/**
* Handles a request to this discovery service.
*
* The IdP disco parameters should be set before calling this function.
*/
public function handleRequest() {
$idp = $this->getTargetIdp();
if($idp !== NULL) {
$extDiscoveryStorage = $this->config->getString('idpdisco.extDiscoveryStorage',NULL);
if ($extDiscoveryStorage !== NULL) {
$this->log('Choice made [' . $idp . '] (Forwarding to external discovery storage)');
SimpleSAML_Utilities::redirectTrustedURL($extDiscoveryStorage, array(
'entityID' => $this->spEntityId,
'IdPentityID' => $idp,
'returnIDParam' => $this->returnIdParam,
'isPassive' => 'true',
'return' => $this->returnURL
));
} else {
$this->log('Choice made [' . $idp . '] (Redirecting the user back. returnIDParam=' . $this->returnIdParam . ')');
SimpleSAML_Utilities::redirectTrustedURL($this->returnURL, array($this->returnIdParam => $idp));
}
return;
}
if ($this->isPassive) {
$this->log('Choice not made. (Redirecting the user back without answer)');
SimpleSAML_Utilities::redirectTrustedURL($this->returnURL);
return;
}
/* No choice made. Show discovery service page. */
$idpList = $this->getIdPList();
$idpList = $this->idplistStructured($this->filterList($idpList));
$preferredIdP = $this->getRecommendedIdP();
$t = new SimpleSAML_XHTML_Template($this->config, 'discopower:disco-tpl.php', 'disco');
$t->data['idplist'] = $idpList;
$t->data['preferredidp'] = $preferredIdP;
$t->data['return'] = $this->returnURL;
$t->data['returnIDParam'] = $this->returnIdParam;
$t->data['entityID'] = $this->spEntityId;
$t->data['urlpattern'] = htmlspecialchars(SimpleSAML_Utilities::selfURLNoQuery());
$t->data['rememberenabled'] = $this->config->getBoolean('idpdisco.enableremember', FALSE);
$t->data['rememberchecked'] = $this->config->getBoolean('idpdisco.rememberchecked', FALSE);
$t->data['defaulttab'] = $this->discoconfig->getValue('defaulttab', 0);
$t->data['score'] = $this->discoconfig->getValue('score', 'quicksilver');
$t->show();
}
/**
* Get the IdP entities saved in the common domain cookie.
*
* @return array List of IdP entities.
*/
private function getCDC() {
if (!isset($_COOKIE['_saml_idp'])) {
return array();
}
$ret = (string)$_COOKIE['_saml_idp'];
$ret = explode(' ', $ret);
foreach ($ret as &$idp) {
$idp = base64_decode($idp);
if ($idp === FALSE) {
/* Not properly base64 encoded. */
return array();
}
}
return $ret;
}
/**
* Save the current IdP choice to a cookie.
*
* This function overrides the corresponding function in the parent class,
* to add support for common domain cookie.
*
* @param string $idp The entityID of the IdP.
*/
protected function setPreviousIdP($idp) {
assert('is_string($idp)');
if ($this->cdcDomain === NULL) {
parent::setPreviousIdP($idp);
return;
}
$list = $this->getCDC();
$prevIndex = array_search($idp, $list, TRUE);
if ($prevIndex !== FALSE) {
unset($list[$prevIndex]);
}
$list[] = $idp;
foreach ($list as &$value) {
$value = base64_encode($value);
}
$newCookie = implode(' ', $list);
while (strlen($newCookie) > 4000) {
/* The cookie is too long. Remove the oldest elements until it is short enough. */
$tmp = explode(' ', $newCookie, 2);
if (count($tmp) === 1) {
/*
* We are left with a single entityID whose base64
* representation is too long to fit in a cookie.
*/
break;
}
$newCookie = $tmp[1];
}
$params = array(
'lifetime' => $this->cdcLifetime,
'domain' => $this->cdcDomain,
'secure' => TRUE,
'httponly' => FALSE,
);
SimpleSAML_Utilities::setCookie('_saml_idp', $newCookie, $params, FALSE);
}
/**
* Retrieve the previous IdP the user used.
*
* This function overrides the corresponding function in the parent class,
* to add support for common domain cookie.
*
* @return string|NULL The entity id of the previous IdP the user used, or NULL if this is the first time.
*/
protected function getPreviousIdP() {
if ($this->cdcDomain === NULL) {
return parent::getPreviousIdP();
}
$prevIdPs = $this->getCDC();
while (count($prevIdPs) > 0) {
$idp = array_pop($prevIdPs);
$idp = $this->validateIdP($idp);
if ($idp !== NULL) {
return $idp;
}
}
return NULL;
}
}