Skip to content
Snippets Groups Projects
Commit 41efcfa8 authored by Jacob Christiansen's avatar Jacob Christiansen
Browse files

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
parent 855c562f
No related branches found
No related tags found
No related merge requests found
...@@ -135,3 +135,64 @@ Example: ...@@ -135,3 +135,64 @@ Example:
'sp2.example.com', '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
)
)
)
)
<?php <?php
/** /**
* Filter for requiring the user to give consent before the attributes are released to the SP. * Filter for requiring the user to give consent before the attributes are released to the SP.
* *
...@@ -54,206 +53,212 @@ ...@@ -54,206 +53,212 @@
*/ */
class sspmod_consent_Auth_Process_Consent extends SimpleSAML_Auth_ProcessingFilter { class sspmod_consent_Auth_Process_Consent extends SimpleSAML_Auth_ProcessingFilter {
/** /**
* Where the focus should be in the form. Can be 'yesbutton', 'nobutton', or NULL. * Where the focus should be in the form. Can be 'yesbutton', 'nobutton', or NULL.
*/ */
private $focus; private $focus;
/** /**
* Whether or not to include attribute values when generates hash * Whether or not to include attribute values when generates hash
*/ */
private $includeValues; private $includeValues;
private $checked; private $checked;
/** /**
* Consent store, if enabled. * Consent store, if enabled.
*/ */
private $store; private $store;
/**
/** * List of attributes where the value should be hidden by default.
* List of attributes where the value should be hidden by default. *
* * @var array
* @var array */
*/ private $hiddenAttributes;
private $hiddenAttributes;
private $_noconsentattributes;
/** /**
* Initialize consent filter. * Initialize consent filter.
* *
* This is the constructor for the consent filter. It validates and parses the configuration. * This is the constructor for the consent filter. It validates and parses the configuration.
* *
* @param array $config Configuration information about this filter. * @param array $config Configuration information about this filter.
* @param mixed $reserved For future use. * @param mixed $reserved For future use.
*/ */
public function __construct($config, $reserved) { public function __construct($config, $reserved) {
parent::__construct($config, $reserved); parent::__construct($config, $reserved);
assert('is_array($config)'); assert('is_array($config)');
$this->includeValues = FALSE; $this->includeValues = FALSE;
if (array_key_exists('includeValues', $config)) { if (array_key_exists('includeValues', $config)) {
$this->includeValues = $config['includeValues']; $this->includeValues = $config['includeValues'];
} }
$this->checked = FALSE; $this->checked = FALSE;
if (array_key_exists('checked', $config)) { if (array_key_exists('checked', $config)) {
$this->checked = $config['checked']; $this->checked = $config['checked'];
} }
if (array_key_exists('focus', $config)) { if (array_key_exists('focus', $config)) {
$this->focus = $config['focus']; $this->focus = $config['focus'];
if (!in_array($this->focus, array('yes', 'no'), TRUE)) { if (!in_array($this->focus, array('yes', 'no'), TRUE)) {
throw new Exception('Invalid value for \'focus\'-parameter to' . throw new Exception('Invalid value for \'focus\'-parameter to' .
' consent:Consent authentication filter: ' . var_export($this->focus, TRUE)); ' consent:Consent authentication filter: ' . var_export($this->focus, TRUE));
} }
} else { } else {
$this->focus = NULL; $this->focus = NULL;
} }
$this->store = NULL; $this->store = NULL;
if (array_key_exists('store', $config)) { if (array_key_exists('store', $config)) {
try { try {
$this->store = sspmod_consent_Store::parseStoreConfig($config['store']); $this->store = sspmod_consent_Store::parseStoreConfig($config['store']);
} catch(Exception $e) { } catch(Exception $e) {
SimpleSAML_Logger::error('Consent - constructor() : Could not create consent storage: ' . $e->getMessage()); SimpleSAML_Logger::error('Consent - constructor() : Could not create consent storage: ' . $e->getMessage());
} }
} }
if (array_key_exists('hiddenAttributes', $config)) { if (array_key_exists('hiddenAttributes', $config)) {
$this->hiddenAttributes = $config['hiddenAttributes']; $this->hiddenAttributes = $config['hiddenAttributes'];
} else { } else {
$this->hiddenAttributes = array(); $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. /**
* * Process a authentication response.
* @param array $state The state of the response. *
*/ * This function saves the state, and redirects the user to the page where the user
public function process(&$state) { * can authorize the release of the attributes.
assert('is_array($state)'); *
assert('array_key_exists("UserID", $state)'); * @param array $state The state of the response.
assert('array_key_exists("Destination", $state)'); */
assert('array_key_exists("entityid", $state["Destination"])'); public function process(&$state) {
assert('array_key_exists("metadata-set", $state["Destination"])'); assert('is_array($state)');
assert('array_key_exists("entityid", $state["Source"])'); assert('array_key_exists("UserID", $state)');
assert('array_key_exists("metadata-set", $state["Source"])'); assert('array_key_exists("Destination", $state)');
assert('array_key_exists("entityid", $state["Destination"])');
$session = SimpleSAML_Session::getInstance(); assert('array_key_exists("metadata-set", $state["Destination"])');
$metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); assert('array_key_exists("entityid", $state["Source"])');
assert('array_key_exists("metadata-set", $state["Source"])');
/* 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 $session = SimpleSAML_Session::getInstance();
* consent module is active on a local IdP and nothing needs to be done. $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler();
*/
if(isset($state['saml:sp:IdP'])) { /* If the consent module is active on a bridge $state['saml:sp:IdP'] will contain
$idpmeta = $metadata->getMetaData($state['saml:sp:IdP'], 'saml20-idp-remote'); * an entry id for the remote IdP. If not, then the
$state['Source'] = $idpmeta; * consent module is active on a local IdP and nothing needs to be done.
} elseif($session->getIdP() !== NULL) { */
/* For backwards compatibility. TODO: Remove in version 1.8. */ if(isset($state['saml:sp:IdP'])) {
$idpmeta = $metadata->getMetaData($session->getIdP(), 'saml20-idp-remote'); $idpmeta = $metadata->getMetaData($state['saml:sp:IdP'], 'saml20-idp-remote');
$state['Source'] = $idpmeta; $state['Source'] = $idpmeta;
} } elseif($session->getIdP() !== NULL) {
/* For backwards compatibility. TODO: Remove in version 1.8. */
if ($this->store !== NULL) { $idpmeta = $metadata->getMetaData($session->getIdP(), 'saml20-idp-remote');
$state['Source'] = $idpmeta;
}
if ($this->store !== NULL) {
// Do not use consent if disabled on source entity // Do not use consent if disabled on source entity
if(isset($state['Source']['consent.disable']) && in_array($state['Destination']['entityid'], $state['Source']['consent.disable'])) { 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']); SimpleSAML_Logger::debug('Consent - Consent disabled for entity ' . $state['Destination']['entityid']);
return; return;
} }
$source = $state['Source']['metadata-set'] . '|' . $state['Source']['entityid']; $source = $state['Source']['metadata-set'] . '|' . $state['Source']['entityid'];
$destination = $state['Destination']['metadata-set'] . '|' . $state['Destination']['entityid']; $destination = $state['Destination']['metadata-set'] . '|' . $state['Destination']['entityid'];
$attributes = $state['Attributes'];
SimpleSAML_Logger::debug('Consent - userid : ' . $state['UserID']);
SimpleSAML_Logger::debug('Consent - source : ' . $source); // Remove attributes that do not require consent
SimpleSAML_Logger::debug('Consent - destination : ' . $destination); foreach($attributes AS $attrkey => $attrval) {
if(in_array($attrkey, $this->_noconsentattributes)) {
$userId = self::getHashedUserID($state['UserID'], $source); unset($attributes[$attrkey]);
$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);
}
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);
}
} }
?>
<?php <?php
/** /**
* Consent script
*
* This script displays a page to the user, which requests that the user * This script displays a page to the user, which requests that the user
* authorizes the release of attributes. * authorizes the release of attributes.
* *
...@@ -8,104 +9,73 @@ ...@@ -8,104 +9,73 @@
* @version $Id$ * @version $Id$
*/ */
/* /**
* Explisit instruct consent page to send no-cache header to browsers * Explicit instruct consent page to send no-cache header to browsers to make
* to make sure user attribute information is not store on client disk. * sure the users attribute information are not store on client disk.
* *
* In an vanilla apache-php installation is the php variables set to: * In an vanilla apache-php installation is the php variables set to:
*
* session.cache_limiter = nocache * session.cache_limiter = nocache
*
* so this is just to make sure. * so this is just to make sure.
*/ */
session_cache_limiter('nocache'); session_cache_limiter('nocache');
$globalConfig = SimpleSAML_Configuration::getInstance();
SimpleSAML_Logger::info('Consent - getconsent: Accessing consent interface'); SimpleSAML_Logger::info('Consent - getconsent: Accessing consent interface');
if (!array_key_exists('StateId', $_REQUEST)) { 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']; $id = $_REQUEST['StateId'];
$state = SimpleSAML_Auth_State::loadState($id, 'consent:request'); $state = SimpleSAML_Auth_State::loadState($id, 'consent:request');
$spentityid = $state['core:SP']; $spentityid = $state['core:SP'];
// The user has pressed the yes-button
if (array_key_exists('yes', $_REQUEST)) { if (array_key_exists('yes', $_REQUEST)) {
/* The user has pressed the yes-button. */ if (array_key_exists('saveconsent', $_REQUEST)) {
SimpleSAML_Logger::stats('consentResponse remember');
if (array_key_exists('saveconsent', $_REQUEST)) { } else {
SimpleSAML_Logger::stats('consentResponse remember'); SimpleSAML_Logger::stats('consentResponse rememberNot');
} else { }
SimpleSAML_Logger::stats('consentResponse rememberNot');
} if ( array_key_exists('consent:store', $state)
&& array_key_exists('saveconsent', $_REQUEST)
if (array_key_exists('consent:store', $state) && array_key_exists('saveconsent', $_REQUEST) && $_REQUEST['saveconsent'] === '1')
&& $_REQUEST['saveconsent'] === '1') { {
/* Save consent. */
/* Save consent. */ $store = $state['consent:store'];
$store = $state['consent:store']; $userId = $state['consent:store.userId'];
$userId = $state['consent:store.userId']; $targetedId = $state['consent:store.destination'];
$targetedId = $state['consent:store.destination']; $attributeSet = $state['consent:store.attributeSet'];
$attributeSet = $state['consent:store.attributeSet'];
SimpleSAML_Logger::debug('Consent - saveConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']');
SimpleSAML_Logger::debug('Consent - saveConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']'); $store->saveConsent($userId, $targetedId, $attributeSet);
$store->saveConsent($userId, $targetedId, $attributeSet); }
}
SimpleSAML_Auth_ProcessingChain::resumeProcessing($state);
SimpleSAML_Auth_ProcessingChain::resumeProcessing($state);
} }
// Prepare attributes for presentation
$attributes = $state['Attributes'];
$noconsentattributes = $state['consent:noconsentattributes'];
/* Prepare attributes for presentation */ // Remove attributes that do not require consent
$attribute_presentation = $state['Attributes']; foreach($attributes AS $attrkey => $attrval) {
if(in_array($attrkey, $noconsentattributes)) {
unset($attributes[$attrkey]);
}
}
$para = array( $para = array(
'attributes' => &$attribute_presentation 'attributes' => &$attributes
); );
/* The callHooks function call below will call a attribute array reordering function if it exist. // Reorder attributes according to attributepresentation hooks
* 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
* )
*
*/
SimpleSAML_Module::callHooks('attributepresentation', $para); SimpleSAML_Module::callHooks('attributepresentation', $para);
/* Make, populate and layout consent form. */ // Make, populate and layout consent form
$globalConfig = SimpleSAML_Configuration::getInstance();
$t = new SimpleSAML_XHTML_Template($globalConfig, 'consent:consentform.php'); $t = new SimpleSAML_XHTML_Template($globalConfig, 'consent:consentform.php');
$t->data['srcMetadata'] = $state['Source']; $t->data['srcMetadata'] = $state['Source'];
$t->data['dstMetadata'] = $state['Destination']; $t->data['dstMetadata'] = $state['Destination'];
...@@ -113,45 +83,45 @@ $t->data['yesTarget'] = SimpleSAML_Module::getModuleURL('consent/getconsent.php' ...@@ -113,45 +83,45 @@ $t->data['yesTarget'] = SimpleSAML_Module::getModuleURL('consent/getconsent.php'
$t->data['yesData'] = array('StateId' => $id); $t->data['yesData'] = array('StateId' => $id);
$t->data['noTarget'] = SimpleSAML_Module::getModuleURL('consent/noconsent.php'); $t->data['noTarget'] = SimpleSAML_Module::getModuleURL('consent/noconsent.php');
$t->data['noData'] = array('StateId' => $id); $t->data['noData'] = array('StateId' => $id);
$t->data['attributes'] = $attribute_presentation; $t->data['attributes'] = $attributes;
$t->data['checked'] = $state['consent:checked']; $t->data['checked'] = $state['consent:checked'];
// Fetch privacypolicy
if (array_key_exists('privacypolicy', $state['Destination'])) { if (array_key_exists('privacypolicy', $state['Destination'])) {
$privacypolicy = $state['Destination']['privacypolicy']; $privacypolicy = $state['Destination']['privacypolicy'];
} elseif (array_key_exists('privacypolicy', $state['Source'])) { } elseif (array_key_exists('privacypolicy', $state['Source'])) {
$privacypolicy = $state['Source']['privacypolicy']; $privacypolicy = $state['Source']['privacypolicy'];
} else { } else {
$privacypolicy = FALSE; $privacypolicy = FALSE;
} }
if($privacypolicy !== FALSE) { if($privacypolicy !== FALSE) {
$privacypolicy = str_replace('%SPENTITYID%', urlencode($spentityid), $privacypolicy = str_replace('%SPENTITYID%', urlencode($spentityid), $privacypolicy);
$privacypolicy);
} }
$t->data['sppp'] = $privacypolicy; $t->data['sppp'] = $privacypolicy;
// Set focus element
switch ($state['consent:focus']) { switch ($state['consent:focus']) {
case NULL: case 'yes':
break; $t->data['autofocus'] = 'yesbutton';
case 'yes': break;
$t->data['autofocus'] = 'yesbutton'; case 'no':
break; $t->data['autofocus'] = 'nobutton';
case 'no': break;
$t->data['autofocus'] = 'nobutton'; case NULL:
break; default:
break;
} }
if (array_key_exists('consent:store', $state)) { if (array_key_exists('consent:store', $state)) {
$t->data['usestorage'] = TRUE; $t->data['usestorage'] = TRUE;
} else { } else {
$t->data['usestorage'] = FALSE; $t->data['usestorage'] = FALSE;
} }
if (array_key_exists('consent:hiddenAttributes', $state)) { if (array_key_exists('consent:hiddenAttributes', $state)) {
$t->data['hiddenAttributes'] = $state['consent:hiddenAttributes']; $t->data['hiddenAttributes'] = $state['consent:hiddenAttributes'];
} else { } else {
$t->data['hiddenAttributes'] = array(); $t->data['hiddenAttributes'] = array();
} }
$t->show(); $t->show();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment