<?php /** * Filter for requiring the user to give consent before the attributes are released to the SP. * * The initial focus of the consent form can be set by setting the 'focus'-attribute to either * 'yes' or 'no'. * * Different storage backends can be configured by setting the 'store'-attribute. The'store'-attribute * is on the form <module>:<class>, and refers to the class sspmod_<module>_Consent_Store_<class>. For * examples, see the built-in modules 'consent:Cookie' and 'consent:Database', which can be found * under modules/consent/lib/Consent/Store. * * Example - minimal: * <code> * 'authproc' => array( * 'consent:Consent', * ), * </code> * * Example - save in cookie: * <code> * 'authproc' => array( * array( * 'consent:Consent', * 'store' => 'consent:Cookie', * ), * </code> * * Example - save in MySQL database: * <code> * 'authproc' => array( * array( * 'consent:Consent', * 'store' => array( * 'consent:Database', * 'dsn' => 'mysql:host=db.example.org;dbname=simplesaml', * 'username' => 'simplesaml', * 'password' => 'secretpassword', * ), * ), * ), * </code> * * Example - initial focus on yes-button: * <code> * 'authproc' => array( * array('consent:Consent', 'focus' => 'yes'), * ), * </code> * * @package simpleSAMLphp * @version $Id$ */ 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; /** * 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']; } 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()); } } } /** * 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 $session->getIdP() will contain * an entry id for the remote IdP. If $session->getIdP() is NULL, then the * consent module is active on a local IdP and nothing needs to be done. */ if($session->getIdP() != null) { $idpmeta = $metadata->getMetaData($session->getIdP(), 'saml20-idp-remote'); $state['Source'] = $idpmeta; } if ($this->store !== NULL) { $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; /* 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 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 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); } } ?>