From 41efcfa86026aa88c26035408d1a431aef0879c5 Mon Sep 17 00:00:00 2001 From: Jacob Christiansen <jach@wayf.dk> Date: Wed, 5 Jan 2011 15:22:32 +0000 Subject: [PATCH] Fixed issue 220 Added the ability to disable consent on certain attributes Minor update to documentation git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2703 44740490-163a-0410-bde0-09ae8108e29a --- modules/consent/docs/consent.txt | 61 +++ modules/consent/lib/Auth/Process/Consent.php | 389 ++++++++++--------- modules/consent/www/getconsent.php | 160 ++++---- 3 files changed, 323 insertions(+), 287 deletions(-) diff --git a/modules/consent/docs/consent.txt b/modules/consent/docs/consent.txt index 3b41fbffc..1dbbcc82b 100644 --- a/modules/consent/docs/consent.txt +++ b/modules/consent/docs/consent.txt @@ -135,3 +135,64 @@ Example: 'sp2.example.com', ... ), + +Attribute presentation +---------------------- + +It is possible to change the way the attributes are represented in the consent +page. This is done by implementing an attribute array reordering function. + +To create this function, you have to create a file named + + hook_attributepresentation.php + +and place it under + + <module_name>/hooks + +directory. To be found and called, the function must be named + + <module_name>_hook_attributepresentation(&$para). + +The parameter $para is an reference to the attribute array. By manipulating +this array you can change the way the attribute are presented to the user on +the consent and status page. + +If you want the attributes to be listed in more than one level, you can make +the function add a child_ prefix to the root node attribute name in a recursive +attribute tree. + +### Examples ### + +These values will be listed as an bullet list + + Array ( + [objectClass] => Array ( + [0] => top + [1] => person + ) + ) + +This array hawe two child array. These will be listed in two separate sub +tables. + + Array ( + [child_eduPersonOrgUnitDN] => Array ( + [0] => Array ( + [ou] => Array ( + [0] => ET + ) + [cn] => Array ( + [0] => Eksterne tjenester + ) + ) + [1] => Array ( + [ou] => Array ( + [0] => TA + ) + [cn] => Array ( + [0] => Tjenesteavdeling + ) + ) + ) + ) diff --git a/modules/consent/lib/Auth/Process/Consent.php b/modules/consent/lib/Auth/Process/Consent.php index ed3a306dc..68cf164c2 100644 --- a/modules/consent/lib/Auth/Process/Consent.php +++ b/modules/consent/lib/Auth/Process/Consent.php @@ -1,5 +1,4 @@ <?php - /** * Filter for requiring the user to give consent before the attributes are released to the SP. * @@ -54,206 +53,212 @@ */ class sspmod_consent_Auth_Process_Consent extends SimpleSAML_Auth_ProcessingFilter { - /** - * Where the focus should be in the form. Can be 'yesbutton', 'nobutton', or NULL. - */ - private $focus; - - /** - * Whether or not to include attribute values when generates hash - */ - private $includeValues; - - private $checked; - - /** - * Consent store, if enabled. - */ - private $store; - - - /** - * List of attributes where the value should be hidden by default. - * - * @var array - */ - private $hiddenAttributes; - - - /** - * Initialize consent filter. - * - * This is the constructor for the consent filter. It validates and parses the configuration. - * - * @param array $config Configuration information about this filter. - * @param mixed $reserved For future use. - */ - public function __construct($config, $reserved) { - parent::__construct($config, $reserved); - assert('is_array($config)'); - - $this->includeValues = FALSE; - if (array_key_exists('includeValues', $config)) { - $this->includeValues = $config['includeValues']; - } - - $this->checked = FALSE; - if (array_key_exists('checked', $config)) { - $this->checked = $config['checked']; - } - - if (array_key_exists('focus', $config)) { - $this->focus = $config['focus']; - if (!in_array($this->focus, array('yes', 'no'), TRUE)) { - throw new Exception('Invalid value for \'focus\'-parameter to' . - ' consent:Consent authentication filter: ' . var_export($this->focus, TRUE)); - } - } else { - $this->focus = NULL; - } - - $this->store = NULL; - if (array_key_exists('store', $config)) { - try { - $this->store = sspmod_consent_Store::parseStoreConfig($config['store']); - } catch(Exception $e) { - SimpleSAML_Logger::error('Consent - constructor() : Could not create consent storage: ' . $e->getMessage()); - } - } - - if (array_key_exists('hiddenAttributes', $config)) { - $this->hiddenAttributes = $config['hiddenAttributes']; - } else { - $this->hiddenAttributes = array(); - } - - } - - - /** - * Process a authentication response. - * - * This function saves the state, and redirects the user to the page where the user - * can authorize the release of the attributes. - * - * @param array $state The state of the response. - */ - public function process(&$state) { - assert('is_array($state)'); - assert('array_key_exists("UserID", $state)'); - assert('array_key_exists("Destination", $state)'); - assert('array_key_exists("entityid", $state["Destination"])'); - assert('array_key_exists("metadata-set", $state["Destination"])'); - assert('array_key_exists("entityid", $state["Source"])'); - assert('array_key_exists("metadata-set", $state["Source"])'); - - $session = SimpleSAML_Session::getInstance(); - $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); - - /* If the consent module is active on a bridge $state['saml:sp:IdP'] will contain - * an entry id for the remote IdP. If not, then the - * consent module is active on a local IdP and nothing needs to be done. - */ - if(isset($state['saml:sp:IdP'])) { - $idpmeta = $metadata->getMetaData($state['saml:sp:IdP'], 'saml20-idp-remote'); - $state['Source'] = $idpmeta; - } elseif($session->getIdP() !== NULL) { - /* For backwards compatibility. TODO: Remove in version 1.8. */ - $idpmeta = $metadata->getMetaData($session->getIdP(), 'saml20-idp-remote'); - $state['Source'] = $idpmeta; - } - - if ($this->store !== NULL) { + /** + * Where the focus should be in the form. Can be 'yesbutton', 'nobutton', or NULL. + */ + private $focus; + + /** + * Whether or not to include attribute values when generates hash + */ + private $includeValues; + + private $checked; + + /** + * Consent store, if enabled. + */ + private $store; + + /** + * List of attributes where the value should be hidden by default. + * + * @var array + */ + private $hiddenAttributes; + + private $_noconsentattributes; + + /** + * Initialize consent filter. + * + * This is the constructor for the consent filter. It validates and parses the configuration. + * + * @param array $config Configuration information about this filter. + * @param mixed $reserved For future use. + */ + public function __construct($config, $reserved) { + parent::__construct($config, $reserved); + assert('is_array($config)'); + + $this->includeValues = FALSE; + if (array_key_exists('includeValues', $config)) { + $this->includeValues = $config['includeValues']; + } + + $this->checked = FALSE; + if (array_key_exists('checked', $config)) { + $this->checked = $config['checked']; + } + + if (array_key_exists('focus', $config)) { + $this->focus = $config['focus']; + if (!in_array($this->focus, array('yes', 'no'), TRUE)) { + throw new Exception('Invalid value for \'focus\'-parameter to' . + ' consent:Consent authentication filter: ' . var_export($this->focus, TRUE)); + } + } else { + $this->focus = NULL; + } + + $this->store = NULL; + if (array_key_exists('store', $config)) { + try { + $this->store = sspmod_consent_Store::parseStoreConfig($config['store']); + } catch(Exception $e) { + SimpleSAML_Logger::error('Consent - constructor() : Could not create consent storage: ' . $e->getMessage()); + } + } + + if (array_key_exists('hiddenAttributes', $config)) { + $this->hiddenAttributes = $config['hiddenAttributes']; + } else { + $this->hiddenAttributes = array(); + } + + if (array_key_exists('noconsentattributes', $config)) { + $this->_noconsentattributes = $config['noconsentattributes']; + } else { + $this->_noconsentattributes = array(); + } + } + + /** + * Process a authentication response. + * + * This function saves the state, and redirects the user to the page where the user + * can authorize the release of the attributes. + * + * @param array $state The state of the response. + */ + public function process(&$state) { + assert('is_array($state)'); + assert('array_key_exists("UserID", $state)'); + assert('array_key_exists("Destination", $state)'); + assert('array_key_exists("entityid", $state["Destination"])'); + assert('array_key_exists("metadata-set", $state["Destination"])'); + assert('array_key_exists("entityid", $state["Source"])'); + assert('array_key_exists("metadata-set", $state["Source"])'); + + $session = SimpleSAML_Session::getInstance(); + $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); + + /* If the consent module is active on a bridge $state['saml:sp:IdP'] will contain + * an entry id for the remote IdP. If not, then the + * consent module is active on a local IdP and nothing needs to be done. + */ + if(isset($state['saml:sp:IdP'])) { + $idpmeta = $metadata->getMetaData($state['saml:sp:IdP'], 'saml20-idp-remote'); + $state['Source'] = $idpmeta; + } elseif($session->getIdP() !== NULL) { + /* For backwards compatibility. TODO: Remove in version 1.8. */ + $idpmeta = $metadata->getMetaData($session->getIdP(), 'saml20-idp-remote'); + $state['Source'] = $idpmeta; + } + + if ($this->store !== NULL) { // Do not use consent if disabled on source entity if(isset($state['Source']['consent.disable']) && in_array($state['Destination']['entityid'], $state['Source']['consent.disable'])) { SimpleSAML_Logger::debug('Consent - Consent disabled for entity ' . $state['Destination']['entityid']); return; } - $source = $state['Source']['metadata-set'] . '|' . $state['Source']['entityid']; - $destination = $state['Destination']['metadata-set'] . '|' . $state['Destination']['entityid']; - - SimpleSAML_Logger::debug('Consent - userid : ' . $state['UserID']); - SimpleSAML_Logger::debug('Consent - source : ' . $source); - SimpleSAML_Logger::debug('Consent - destination : ' . $destination); - - $userId = self::getHashedUserID($state['UserID'], $source); - $targetedId = self::getTargetedID($state['UserID'], $source, $destination); - $attributeSet = self::getAttributeHash($state['Attributes'], $this->includeValues); - - SimpleSAML_Logger::debug('Consent - hasConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']'); - if ($this->store->hasConsent($userId, $targetedId, $attributeSet)) { - - SimpleSAML_Logger::stats('consent found'); - - /* Consent already given. */ - return; - } - SimpleSAML_Logger::stats('consent notfound'); - - $state['consent:store'] = $this->store; - $state['consent:store.userId'] = $userId; - $state['consent:store.destination'] = $targetedId; - $state['consent:store.attributeSet'] = $attributeSet; - - } else { - SimpleSAML_Logger::stats('consent nostorage'); - } - - $state['consent:focus'] = $this->focus; - $state['consent:checked'] = $this->checked; - $state['consent:hiddenAttributes'] = $this->hiddenAttributes; - - /* User interaction nessesary. Throw exception on isPassive request */ - if (isset($state['isPassive']) && $state['isPassive'] == TRUE) { - throw new SimpleSAML_Error_NoPassive('Unable to give consent on passive request.'); - } - - /* Save state and redirect. */ - $id = SimpleSAML_Auth_State::saveState($state, 'consent:request'); - $url = SimpleSAML_Module::getModuleURL('consent/getconsent.php'); - SimpleSAML_Utilities::redirect($url, array('StateId' => $id)); - } - - - /** - * Generate a globally unique identifier of the user. Will also be anonymous (hashed). - * - * @return hash( eduPersonPrincipalName + salt + IdP-identifier ) - */ - public static function getHashedUserID($userid, $source) { - return hash('sha1', $userid . '|' . SimpleSAML_Utilities::getSecretSalt() . '|' . $source ); - } - - /** - * Get a targeted ID. An identifier that is unique per SP entity ID. - */ - public static function getTargetedID($userid, $source, $destination) { - return hash('sha1', $userid . '|' . SimpleSAML_Utilities::getSecretSalt() . '|' . $source . '|' . $destination); - } - - /** - * Get a hash value that changes when attributes are added or attribute values changed. - * @param boolean $includeValues Whether or not to include the attribute value in the generation of the hash. - */ - public static function getAttributeHash($attributes, $includeValues = FALSE) { - - $hashBase = NULL; - if ($includeValues) { - ksort($attributes); - $hashBase = serialize($attributes); - } else { - $names = array_keys($attributes); - sort($names); - $hashBase = implode('|', $names); - } - return hash('sha1', $hashBase); - } + $source = $state['Source']['metadata-set'] . '|' . $state['Source']['entityid']; + $destination = $state['Destination']['metadata-set'] . '|' . $state['Destination']['entityid']; + $attributes = $state['Attributes']; + + // Remove attributes that do not require consent + foreach($attributes AS $attrkey => $attrval) { + if(in_array($attrkey, $this->_noconsentattributes)) { + unset($attributes[$attrkey]); + } + } + SimpleSAML_Logger::debug('Consent - userid : ' . $state['UserID']); + SimpleSAML_Logger::debug('Consent - source : ' . $source); + SimpleSAML_Logger::debug('Consent - destination : ' . $destination); + $userId = self::getHashedUserID($state['UserID'], $source); + $targetedId = self::getTargetedID($state['UserID'], $source, $destination); + $attributeSet = self::getAttributeHash($attributes, $this->includeValues); + SimpleSAML_Logger::debug('Consent - hasConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']'); + if ($this->store->hasConsent($userId, $targetedId, $attributeSet)) { + SimpleSAML_Logger::stats('consent found'); + /* Consent already given. */ + return; + } + SimpleSAML_Logger::stats('consent notfound'); + + $state['consent:store'] = $this->store; + $state['consent:store.userId'] = $userId; + $state['consent:store.destination'] = $targetedId; + $state['consent:store.attributeSet'] = $attributeSet; + + } else { + SimpleSAML_Logger::stats('consent nostorage'); + } + + $state['consent:focus'] = $this->focus; + $state['consent:checked'] = $this->checked; + $state['consent:hiddenAttributes'] = $this->hiddenAttributes; + $state['consent:noconsentattributes'] = $this->_noconsentattributes; + + /* User interaction nessesary. Throw exception on isPassive request */ + if (isset($state['isPassive']) && $state['isPassive'] == TRUE) { + throw new SimpleSAML_Error_NoPassive('Unable to give consent on passive request.'); + } + + /* Save state and redirect. */ + $id = SimpleSAML_Auth_State::saveState($state, 'consent:request'); + $url = SimpleSAML_Module::getModuleURL('consent/getconsent.php'); + SimpleSAML_Utilities::redirect($url, array('StateId' => $id)); + } + + + /** + * Generate a globally unique identifier of the user. Will also be anonymous (hashed). + * + * @return hash( eduPersonPrincipalName + salt + IdP-identifier ) + */ + public static function getHashedUserID($userid, $source) { + return hash('sha1', $userid . '|' . SimpleSAML_Utilities::getSecretSalt() . '|' . $source ); + } + + /** + * Get a targeted ID. An identifier that is unique per SP entity ID. + */ + public static function getTargetedID($userid, $source, $destination) { + return hash('sha1', $userid . '|' . SimpleSAML_Utilities::getSecretSalt() . '|' . $source . '|' . $destination); + } + + /** + * Get a hash value that changes when attributes are added or attribute values changed. + * @param boolean $includeValues Whether or not to include the attribute value in the generation of the hash. + */ + public static function getAttributeHash($attributes, $includeValues = FALSE) { + + $hashBase = NULL; + if ($includeValues) { + ksort($attributes); + $hashBase = serialize($attributes); + } else { + $names = array_keys($attributes); + sort($names); + $hashBase = implode('|', $names); + } + return hash('sha1', $hashBase); + } } - -?> diff --git a/modules/consent/www/getconsent.php b/modules/consent/www/getconsent.php index 7e9819d7c..8363917ae 100644 --- a/modules/consent/www/getconsent.php +++ b/modules/consent/www/getconsent.php @@ -1,6 +1,7 @@ <?php - /** + * Consent script + * * This script displays a page to the user, which requests that the user * authorizes the release of attributes. * @@ -8,104 +9,73 @@ * @version $Id$ */ -/* - * Explisit instruct consent page to send no-cache header to browsers - * to make sure user attribute information is not store on client disk. +/** + * Explicit instruct consent page to send no-cache header to browsers to make + * sure the users attribute information are not store on client disk. * * In an vanilla apache-php installation is the php variables set to: + * * session.cache_limiter = nocache + * * so this is just to make sure. */ session_cache_limiter('nocache'); +$globalConfig = SimpleSAML_Configuration::getInstance(); + SimpleSAML_Logger::info('Consent - getconsent: Accessing consent interface'); if (!array_key_exists('StateId', $_REQUEST)) { - throw new SimpleSAML_Error_BadRequest('Missing required StateId query parameter.'); + throw new SimpleSAML_Error_BadRequest('Missing required StateId query parameter.'); } $id = $_REQUEST['StateId']; $state = SimpleSAML_Auth_State::loadState($id, 'consent:request'); $spentityid = $state['core:SP']; +// The user has pressed the yes-button if (array_key_exists('yes', $_REQUEST)) { - /* The user has pressed the yes-button. */ - - if (array_key_exists('saveconsent', $_REQUEST)) { - SimpleSAML_Logger::stats('consentResponse remember'); - } else { - SimpleSAML_Logger::stats('consentResponse rememberNot'); - } - - if (array_key_exists('consent:store', $state) && array_key_exists('saveconsent', $_REQUEST) - && $_REQUEST['saveconsent'] === '1') { - - /* Save consent. */ - $store = $state['consent:store']; - $userId = $state['consent:store.userId']; - $targetedId = $state['consent:store.destination']; - $attributeSet = $state['consent:store.attributeSet']; - - SimpleSAML_Logger::debug('Consent - saveConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']'); - $store->saveConsent($userId, $targetedId, $attributeSet); - } - - SimpleSAML_Auth_ProcessingChain::resumeProcessing($state); + if (array_key_exists('saveconsent', $_REQUEST)) { + SimpleSAML_Logger::stats('consentResponse remember'); + } else { + SimpleSAML_Logger::stats('consentResponse rememberNot'); + } + + if ( array_key_exists('consent:store', $state) + && array_key_exists('saveconsent', $_REQUEST) + && $_REQUEST['saveconsent'] === '1') + { + /* Save consent. */ + $store = $state['consent:store']; + $userId = $state['consent:store.userId']; + $targetedId = $state['consent:store.destination']; + $attributeSet = $state['consent:store.attributeSet']; + + SimpleSAML_Logger::debug('Consent - saveConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']'); + $store->saveConsent($userId, $targetedId, $attributeSet); + } + + SimpleSAML_Auth_ProcessingChain::resumeProcessing($state); } +// Prepare attributes for presentation +$attributes = $state['Attributes']; +$noconsentattributes = $state['consent:noconsentattributes']; -/* Prepare attributes for presentation */ -$attribute_presentation = $state['Attributes']; +// Remove attributes that do not require consent +foreach($attributes AS $attrkey => $attrval) { + if(in_array($attrkey, $noconsentattributes)) { + unset($attributes[$attrkey]); + } +} $para = array( - 'attributes' => &$attribute_presentation + 'attributes' => &$attributes ); -/* The callHooks function call below will call a attribute array reordering function if it exist. - * To create this function, you hawe to create a file with name: hook_attributepresentation.php and place - * it under a <module_dir>/hooks directory. To be found and called, the function hawe to - * be named : <module_name>_hook_attributepresentation(&$para). - * The parameter $para is an reference to the attribute array. By manipulating this array - * you change the way the attribute is presented to the user on the consent and status page. - * If you want to have the attributes listed in more than one level. You can make the function add - * a child_ prefix to the root node attribute name in a recursive attribute tree. - * In the array below is an example of this: - * - * Array - * ( - * [objectClass] => Array - * ( - * [0] => top <--- These values will be listed as an bullet list - * [1] => person - * ) - * [child_eduPersonOrgUnitDN] => Array <--- This array hawe two child array. These will be listed in - * ( two separate sub tables. - * [0] => Array - * ( - * [ou] => Array - * ( - * [0] => ET - * ) - * [cn] => Array - * ( - * [0] => Eksterne tjenester - * ) - * [1] => Array - * ( - * [ou] => Array - * ( - * [0] => TA - * ) - * [cn] => Array - * ( - * [0] => Tjenesteavdeling - * ) - * - */ +// Reorder attributes according to attributepresentation hooks SimpleSAML_Module::callHooks('attributepresentation', $para); -/* Make, populate and layout consent form. */ - -$globalConfig = SimpleSAML_Configuration::getInstance(); +// Make, populate and layout consent form $t = new SimpleSAML_XHTML_Template($globalConfig, 'consent:consentform.php'); $t->data['srcMetadata'] = $state['Source']; $t->data['dstMetadata'] = $state['Destination']; @@ -113,45 +83,45 @@ $t->data['yesTarget'] = SimpleSAML_Module::getModuleURL('consent/getconsent.php' $t->data['yesData'] = array('StateId' => $id); $t->data['noTarget'] = SimpleSAML_Module::getModuleURL('consent/noconsent.php'); $t->data['noData'] = array('StateId' => $id); -$t->data['attributes'] = $attribute_presentation; - +$t->data['attributes'] = $attributes; $t->data['checked'] = $state['consent:checked']; +// Fetch privacypolicy if (array_key_exists('privacypolicy', $state['Destination'])) { - $privacypolicy = $state['Destination']['privacypolicy']; + $privacypolicy = $state['Destination']['privacypolicy']; } elseif (array_key_exists('privacypolicy', $state['Source'])) { - $privacypolicy = $state['Source']['privacypolicy']; + $privacypolicy = $state['Source']['privacypolicy']; } else { - $privacypolicy = FALSE; + $privacypolicy = FALSE; } if($privacypolicy !== FALSE) { - $privacypolicy = str_replace('%SPENTITYID%', urlencode($spentityid), - $privacypolicy); + $privacypolicy = str_replace('%SPENTITYID%', urlencode($spentityid), $privacypolicy); } $t->data['sppp'] = $privacypolicy; - - +// Set focus element switch ($state['consent:focus']) { - case NULL: - break; - case 'yes': - $t->data['autofocus'] = 'yesbutton'; - break; - case 'no': - $t->data['autofocus'] = 'nobutton'; - break; + case 'yes': + $t->data['autofocus'] = 'yesbutton'; + break; + case 'no': + $t->data['autofocus'] = 'nobutton'; + break; + case NULL: + default: + break; } if (array_key_exists('consent:store', $state)) { - $t->data['usestorage'] = TRUE; + $t->data['usestorage'] = TRUE; } else { - $t->data['usestorage'] = FALSE; + $t->data['usestorage'] = FALSE; } + if (array_key_exists('consent:hiddenAttributes', $state)) { - $t->data['hiddenAttributes'] = $state['consent:hiddenAttributes']; + $t->data['hiddenAttributes'] = $state['consent:hiddenAttributes']; } else { - $t->data['hiddenAttributes'] = array(); + $t->data['hiddenAttributes'] = array(); } $t->show(); -- GitLab