diff --git a/modules/consent/default-disable b/modules/consent/default-disable
new file mode 100644
index 0000000000000000000000000000000000000000..fa0bd82e2df7bd79d57593d35bc53c1f9d3ef71f
--- /dev/null
+++ b/modules/consent/default-disable
@@ -0,0 +1,3 @@
+This file indicates that the default state of this module
+is disabled. To enable, create a file named enable in the
+same directory as this file.
diff --git a/modules/consent/lib/Auth/Process/Consent.php b/modules/consent/lib/Auth/Process/Consent.php
new file mode 100644
index 0000000000000000000000000000000000000000..e95b2aff098dfe4da20e62a01c5fc53c1d671824
--- /dev/null
+++ b/modules/consent/lib/Auth/Process/Consent.php
@@ -0,0 +1,145 @@
+<?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;
+
+
+	/**
+	 * 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)');
+
+		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;
+		}
+
+		if (array_key_exists('store', $config)) {
+			$this->store = sspmod_consent_Store::parseStoreConfig($config['store']);
+		} else {
+			$this->store = NULL;
+		}
+
+	}
+
+
+	/**
+	 * 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"])');
+
+		if ($this->store !== NULL) {
+			$userId = sha1($state['UserID'] . SimpleSAML_Utilities::getSecretSalt());;
+			$destination = $state['Destination']['metadata-set'] . '/' . $state['Destination']['entityid'];
+
+			$attributeSet = array_keys($state['Attributes']);
+			sort($attributeSet);
+			$attributeSet = implode(',', $attributeSet);
+			$attributeSet = sha1($attributeSet);
+
+			if ($this->store->hasConsent($userId, $destination, $attributeSet)) {
+				/* Consent already given. */
+				return;
+			}
+
+			$state['consent:store'] = $this->store;
+			$state['consent:store.userId'] = $userId;
+			$state['consent:store.destination'] = $destination;
+			$state['consent:store.attributeSet'] = $attributeSet;
+		}
+
+		$state['consent:focus'] = $this->focus;
+
+		/* 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));
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/modules/consent/lib/Consent/Store/Cookie.php b/modules/consent/lib/Consent/Store/Cookie.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d96f893031544e602cd4550f9895995a7f8a364
--- /dev/null
+++ b/modules/consent/lib/Consent/Store/Cookie.php
@@ -0,0 +1,247 @@
+<?php
+
+/**
+ * Store consent in cookies.
+ *
+ * This class implements a consent store which stores the consent information in
+ * cookies on the users computer.
+ *
+ * Example - Consent module with cookie store:
+ * <code>
+ * 'authproc' => array(
+ *   array(
+ *     'consent:Consent',
+ *     'store' => 'consent:Cookie',
+ *     ),
+ *   ),
+ * </code>
+ *
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_consent_Consent_Store_Cookie extends sspmod_consent_Store {
+
+
+	/**
+	 * Check for consent.
+	 *
+	 * This function checks whether a given user has authorized the release of the attributes
+	 * identified by $attributeSet from $source to $destination.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifies the destination.
+	 * @param string $attributeSet  A hash which identifies the attributes.
+	 * @return bool  TRUE if the user has given consent earlier, FALSE if not (or on error).
+	 */
+	public function hasConsent($userId, $destinationId, $attributeSet) {
+		assert('is_string($userId)');
+		assert('is_string($destinationId)');
+		assert('is_string($attributeSet)');
+
+		$cookieName = self::getCookieName($userId, $destinationId);
+
+		if (!array_key_exists($cookieName, $_COOKIE)) {
+			SimpleSAML_Logger::debug('Consent cookie - no cookie with name \'' . $cookieName . '\'.');
+			return FALSE;
+		}
+		if (!is_string($_COOKIE[$cookieName])) {
+			SimpleSAML_Logger::warning('Value of consent cookie wasn\'t a string. Was: ' . var_export($_COOKIE[$cookieName], TRUE));
+			return FALSE;
+		}
+
+		$data = $userId . ':' . $attributeSet . ':' . $destinationId;
+		$data = self::sign($data);
+
+		if ($_COOKIE[$cookieName] !== $data) {
+			SimpleSAML_Logger::info('Attribute set changed from the last time consent was given.');
+			return FALSE;
+		}
+
+		SimpleSAML_Logger::debug('Consent cookie - found cookie with correct name and value.');
+
+		return TRUE;
+	}
+
+
+	/**
+	 * Save consent.
+	 *
+	 * Called when the user asks for the consent to be saved. If consent information
+	 * for the given user and destination already exists, it should be overwritten.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifies the destination.
+	 * @param string $attributeSet  A hash which identifies the attributes.
+	 */
+	public function saveConsent($userId, $destinationId, $attributeSet) {
+		assert('is_string($userId)');
+		assert('is_string($destinationId)');
+		assert('is_string($attributeSet)');
+
+		$name = self::getCookieName($userId, $destinationId);
+		$value = $userId . ':' . $attributeSet . ':' . $destinationId;
+		$value = self::sign($value);
+		$this->setConsentCookie($name, $value);
+	}
+
+
+	/**
+	 * Delete consent.
+	 *
+	 * Called when a user revokes consent for a given destination.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifies the destination.
+	 */
+	public function deleteConsent($userId, $destinationId) {
+		assert('is_string($userId)');
+		assert('is_string($destinationId)');
+
+		$name = self::getCookieName($userId, $destinationId);
+		$this->setConsentCookie($name, NULL);
+
+	}
+
+
+	/**
+	 * Retrieve consents.
+	 *
+	 * This function should return a list of consents the user has saved.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @return array  Array of all destination ids the user has given consent for.
+	 */
+	public function getConsents($userId) {
+		assert('is_string($userId)');
+
+		$ret = array();
+
+		$cookieNameStart = 'sspmod_consent:';
+		$cookieNameStartLen = strlen($cookieNameStart);
+		foreach ($_COOKIE as $name => $value) {
+
+			if (substr($name, 0, $cookieNameStartLen) !== $cookieNameStart) {
+				continue;
+			}
+
+			$value = self::verify($value);
+			if ($value === FALSE) {
+				continue;
+			}
+
+			$tmp = explode(':', $value, 3);
+			if (count($tmp) !== 3) {
+				SimpleSAML_Logger::warning('Consent cookie with invalid value: ' . $value);
+				continue;
+			}
+
+			if ($userId !== $tmp[0]) {
+				/* Wrong user. */
+				continue;
+			}
+
+			$destination = $tmp[2];
+
+
+			$ret[] = $destination;
+		}
+
+		return $ret;
+	}
+
+
+	/**
+	 * Calculate a signature of some data.
+	 *
+	 * This function calculates a signature of the data.
+	 *
+	 * @param string $data  The data which should be signed.
+	 * @return string  The signed data.
+	 */
+	private static function sign($data) {
+		assert('is_string($data)');
+
+		$secretSalt = SimpleSAML_Utilities::getSecretSalt();
+
+		return sha1($secretSalt . $data . $secretSalt) . ':' . $data;
+	}
+
+
+	/**
+	 * Verify signed data.
+	 *
+	 * This function verifies signed data.
+	 *
+	 * @param string $signedData  The data which is signed.
+	 * @return string|FALSE  The data, or FALSE if the signature is invalid.
+	 */
+	private static function verify($signedData) {
+		assert('is_string($signedData)');
+
+		$data = explode(':', $signedData, 2);
+		if (count($data) !== 2) {
+			SimpleSAML_Logger::warning('Consent cookie: Missing signature.');
+			return FALSE;
+		}
+		$data = $data[1];
+
+		$newSignedData = self::sign($data);
+		if ($newSignedData !== $signedData) {
+			SimpleSAML_Logger::warning('Consent cookie: Invalid signature.');
+			return FALSE;
+		}
+
+		return $data;
+	}
+
+
+	/**
+	 * Get cookie name.
+	 *
+	 * This function gets the cookie name for the given user & destination.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifies the destination.
+	 */
+	private static function getCookieName($userId, $destinationId) {
+		assert('is_string($userId)');
+		assert('is_string($destinationId)');
+
+		return 'sspmod_consent:' . sha1($userId . ':' . $destinationId);
+	}
+
+
+	/**
+	 * Helper function for setting a cookie.
+	 *
+	 * @param string $name  Name of the cookie.
+	 * @param string|NULL $value  Value of the cookie. Set this to NULL to delete the cookie.
+	 */
+	private function setConsentCookie($name, $value) {
+		assert('is_string($name)');
+		assert('is_string($value)');
+
+		if ($value === NULL) {
+			$expire = 1; /* Delete by setting expiry in the past. */
+			$value = '';
+		} else {
+			$expire = time() + 90 * 24*60*60;
+		}
+
+		if (SimpleSAML_Utilities::isHTTPS()) {
+			/* Enable secure cookie for https-requests. */
+			$secure = TRUE;
+		} else {
+			$secure = FALSE;
+		}
+
+		$globalConfig = SimpleSAML_Configuration::getInstance();
+		$path = '/' . $globalConfig->getBaseURL();
+
+		setcookie($name, $value, $expire, $path, NULL, $secure);
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/modules/consent/lib/Consent/Store/Database.php b/modules/consent/lib/Consent/Store/Database.php
new file mode 100644
index 0000000000000000000000000000000000000000..337713aa46ee5df9379cfb2e95e13f88c63194ef
--- /dev/null
+++ b/modules/consent/lib/Consent/Store/Database.php
@@ -0,0 +1,378 @@
+<?php
+
+/**
+ * Store consent in database.
+ *
+ * This class implements a consent store which stores the consent information in
+ * a database. It is tested, and should work against both MySQL and PostgreSQL.
+ *
+ * It has the following options:
+ * - dsn: The DSN which should be used to connect to the database server. Check the various
+ *        database drivers in http://php.net/manual/en/pdo.drivers.php for a description of
+ *        the various DSN formats.
+ * - username: The username which should be used when connecting to the database server.
+ * - password: The password which should be used when connecting to the database server.
+ * - table: The name of the table. Optional, defaults to 'ssp_consent'.
+ *
+ * Example - consent module with 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 - consent module with PostgreSQL database:
+ * <code>
+ * 'authproc' => array(
+ *   array(
+ *     'consent:Consent',
+ *     'store' => array(
+ *       'consent:Database',
+ *       'dsn' => 'pgsql:host=db.example.org;port=5432;dbname=simplesaml',
+ *       'username' => 'simplesaml',
+ *       'password' => 'secretpassword',
+ *       ),
+ *     ),
+ *   ),
+ * </code>
+ *
+ *
+ * Table declaration:
+ * CREATE TABLE ssp_consent (
+ *   consentTime TIMESTAMP NOT NULL,
+ *   lastUse TIMESTAMP NOT NULL,
+ *   userId VARCHAR(80) NOT NULL,
+ *   destinationId VARCHAR(255) NOT NULL,
+ *   attributeSet VARCHAR(80) NOT NULL,
+ *   UNIQUE (userId, destinationId)
+ * );
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class sspmod_consent_Consent_Store_Database extends sspmod_consent_Store {
+
+
+	/**
+	 * DSN for the database.
+	 */
+	private $dsn;
+
+
+	/**
+	 * Username for the database.
+	 */
+	private $username;
+
+
+	/**
+	 * Password for the database;
+	 */
+	private $password;
+
+
+	/**
+	 * Table with consent.
+	 */
+	private $table;
+
+
+	/**
+	 * Database handle.
+	 *
+	 * This variable can't be serialized.
+	 */
+	private $db;
+
+
+	/**
+	 * Parse configuration.
+	 *
+	 * This constructor parses the configuration.
+	 *
+	 * @param array $config  Configuration for database consent store.
+	 */
+	public function __construct($config) {
+		parent::__construct($config);
+
+		foreach (array('dsn', 'username', 'password') as $id) {
+			if (!array_key_exists($id, $config)) {
+				throw new Exception('consent:Database - Missing required option \'' . $id . '\'.');
+			}
+			if (!is_string($config[$id])) {
+				throw new Exception('consent:Database - \'' . $id . '\' is supposed to be a string.');
+			}
+		}
+
+		$this->dsn = $config['dsn'];
+		$this->username = $config['username'];
+		$this->password = $config['password'];
+
+		if (array_key_exists('table', $config)) {
+			if (!is_string($config['table'])) {
+				throw new Exception('consent:Database - \'table\' is supposed to be a string.');
+			}
+			$this->table = $config['table'];
+		} else {
+			$this->table = 'ssp_consent';
+		}
+	}
+
+
+	/**
+	 * Called before serialization.
+	 *
+	 * @return array  The variables which should be serialized.
+	 */
+	public function __sleep() {
+
+		return array(
+			'dsn',
+			'username',
+			'password',
+			'table',
+			);
+	}
+
+
+	/**
+	 * Check for consent.
+	 *
+	 * This function checks whether a given user has authorized the release of the attributes
+	 * identified by $attributeSet from $source to $destination.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifies the destination.
+	 * @param string $attributeSet  A hash which identifies the attributes.
+	 * @return bool  TRUE if the user has given consent earlier, FALSE if not (or on error).
+	 */
+	public function hasConsent($userId, $destinationId, $attributeSet) {
+		assert('is_string($userId)');
+		assert('is_string($destinationId)');
+		assert('is_string($attributeSet)');
+
+		$st = $this->execute('UPDATE ' . $this->table . ' SET lastUse = NOW() WHERE userId = ? AND destinationId = ? AND attributeSet = ?',
+			array($userId, $destinationId, $attributeSet));
+		if ($st === FALSE) {
+			return FALSE;
+		}
+
+		$rowCount = $st->rowCount();
+		if ($rowCount === 0) {
+			SimpleSAML_Logger::debug('consent:Database - No consent found.');
+			return FALSE;
+		} else {
+			SimpleSAML_Logger::debug('consent:Database - Consent found.');
+			return TRUE;
+		}
+
+	}
+
+
+	/**
+	 * Save consent.
+	 *
+	 * Called when the user asks for the consent to be saved. If consent information
+	 * for the given user and destination already exists, it should be overwritten.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifies the destination.
+	 * @param string $attributeSet  A hash which identifies the attributes.
+	 */
+	public function saveConsent($userId, $destinationId, $attributeSet) {
+		assert('is_string($userId)');
+		assert('is_string($destinationId)');
+		assert('is_string($attributeSet)');
+
+		/* Check for old consent (with different attribute set). */
+		$st = $this->execute('UPDATE ' . $this->table . ' SET consentTime = NOW(), lastUse = NOW(), attributeSet = ? WHERE userId = ? AND destinationId = ?',
+			array($attributeSet, $userId, $destinationId));
+		if ($st === FALSE) {
+			return;
+		}
+		if ($st->rowCount() > 0) {
+			/* We had already stored consent for the given destination in the database. */
+			SimpleSAML_Logger::debug('consent:Database - Updated old consent.');
+			return;
+		}
+
+		/* Add new consent. We don't check for error since there is nothing we can do if one occurs. */
+		$st = $this->execute('INSERT INTO ' . $this->table . ' (consentTime, lastUse, userId, destinationId, attributeSet) VALUES(NOW(),NOW(),?,?,?)',
+			array($userId, $destinationId, $attributeSet));
+		if ($st !== FALSE) {
+			SimpleSAML_Logger::debug('consent:Database - Saved new consent.');
+		}
+	}
+
+
+	/**
+	 * Delete consent.
+	 *
+	 * Called when a user revokes consent for a given destination.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifies the destination.
+	 */
+	public function deleteConsent($userId, $destinationId) {
+		assert('is_string($userId)');
+		assert('is_string($destinationId)');
+
+		$st = $this->execute('DELETE FROM ' . $this->table . ' WHERE userId = ? and destinationId = ?',
+			array($userId, $destinationId));
+		if ($st === FALSE) {
+			return;
+		}
+
+		if ($st->rowCount() > 0) {
+			SimpleSAML_Logger::debug('consent:Database - Deleted consent.');
+		} else {
+			SimpleSAML_Logger::warning('consent:Database - Attempted to delete nonexistent consent');
+		}
+	}
+
+
+	/**
+	 * Retrieve consents.
+	 *
+	 * This function should return a list of consents the user has saved.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @return array  Array of all destination ids the user has given consent for.
+	 */
+	public function getConsents($userId) {
+		assert('is_string($userId)');
+
+		$ret = array();
+
+		$st = $this->execute('SELECT destinationId FROM ' . $this->table . ' WHERE userId = ?',
+			array($userId));
+		if ($st === FALSE) {
+			return array();
+		}
+
+		while ($row = $st->fetch(PDO::FETCH_NUM)) {
+			$ret[] = $row[0];
+		}
+
+		return $ret;
+	}
+
+
+	/**
+	 * Prepare and execute statement.
+	 *
+	 * This function prepares and executes a statement. On error, FALSE will be returned.
+	 *
+	 * @param string $statement  The statement which should be executed.
+	 * @param array $parameters  Parameters for the statement.
+	 * @return PDOStatement|FALSE  The statement, or FALSE if execution failed.
+	 */
+	private function execute($statement, $parameters) {
+		assert('is_string($statement)');
+		assert('is_array($parameters)');
+
+		$db = $this->getDB();
+		if ($db === FALSE) {
+			return FALSE;
+		}
+
+		$st = $db->prepare($statement);
+		if ($st === FALSE) {
+			if ($st === FALSE) {
+				SimpleSAML_Logger::error('consent:Database - Error preparing statement \'' .
+					$statement . '\': ' . self::formatError($db->errorInfo()));
+				return FALSE;
+			}
+		}
+
+		if ($st->execute($parameters) !== TRUE) {
+			SimpleSAML_Logger::error('consent:Database - Error executing statement \'' .
+				$statement . '\': ' . self::formatError($st->errorInfo()));
+			return FALSE;
+		}
+
+		return $st;
+	}
+
+
+	/**
+	 * Create consent table.
+	 *
+	 * This function creates the table with consent data.
+	 *
+	 * @return TRUE if successful, FALSE if not.
+	 */
+	private function createTable() {
+
+		$db = $this->getDB();
+		if ($db === FALSE) {
+			return FALSE;
+		}
+
+		$res = $this->db->exec(
+			'CREATE TABLE ' . $this->table . ' (' .
+			'consentTime TIMESTAMP NOT NULL,' .
+			'lastUse TIMESTAMP NOT NULL,' .
+			'userId VARCHAR(80) NOT NULL,' .
+			'destinationId VARCHAR(255) NOT NULL,' .
+			'attributeSet VARCHAR(80) NOT NULL,' .
+			'UNIQUE (userId, destinationId)' .
+			')');
+		if ($res === FALSE) {
+			SimpleSAML_Logger::error('consent:Database - Failed to create table \'' . $this->table . '\'.');
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+
+	/**
+	 * Get database handle.
+	 *
+	 * @return PDO|FALSE  Database handle, or FALSE if we fail to connect.
+	 */
+	private function getDB() {
+
+		if ($this->db !== NULL) {
+			return $this->db;
+		}
+
+		try {
+			$this->db = new PDO($this->dsn, $this->username, $this->password);
+		} catch (PDOException $e) {
+			SimpleSAML_Logger::error('consent:Database - Failed to connect to \'' .
+				$this->dsn . '\': '. $e->getMessage());
+			$this->db = FALSE;
+		}
+
+		return $this->db;
+	}
+
+
+	/**
+	 * Format PDO error.
+	 *
+	 * This function formats a PDO error, as returned from errorInfo.
+	 *
+	 * @param array $error  The error information.
+	 * @return string  Error text.
+	 */
+	private static function formatError($error) {
+		assert('is_array($error)');
+		assert('count($error) >= 3');
+
+		return $error[0] . ' - ' . $error[2] . ' (' . $error[1] . ')';
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/modules/consent/lib/Store.php b/modules/consent/lib/Store.php
new file mode 100644
index 0000000000000000000000000000000000000000..c4ffe233f330b70373766c9b2758314696c49be3
--- /dev/null
+++ b/modules/consent/lib/Store.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * Base class for consent storage handlers.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class sspmod_consent_Store {
+
+
+	/**
+	 * Constructor for the base class.
+	 *
+	 * This constructor should always be called first in any class which implements
+	 * this class.
+	 *
+	 * @param array &$config  The configuration for this storage handler..
+	 */
+	protected function __construct(&$config) {
+		assert('is_array($config)');
+	}
+
+
+	/**
+	 * Check for consent.
+	 *
+	 * This function checks whether a given user has authorized the release of the attributes
+	 * identified by $attributeSet from $source to $destination.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifyes the destination.
+	 * @param string $attributeSet  A hash which identifies the attributes.
+	 * @return bool  TRUE if the user has given consent earlier, FALSE if not (or on error).
+	 */
+	abstract public function hasConsent($userId, $destinationId, $attributeSet);
+
+
+	/**
+	 * Save consent.
+	 *
+	 * Called when the user asks for the consent to be saved. If consent information
+	 * for the given user and destination already exists, it should be overwritten.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifyes the destination.
+	 * @param string $attributeSet  A hash which identifies the attributes.
+	 */
+	abstract public function saveConsent($userId, $destinationId, $attributeSet);
+
+
+	/**
+	 * Delete consent.
+	 *
+	 * Called when a user revokes consent for a given destination.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @param string $destinationId  A string which identifyes the destination.
+	 */
+	abstract public function deleteConsent($userId, $destinationId);
+
+
+	/**
+	 * Retrieve consents.
+	 *
+	 * This function should return a list of consents the user has saved.
+	 *
+	 * @param string $userId  The hash identifying the user at an IdP.
+	 * @return array  Array of all destination ids the user has given consent for.
+	 */
+	abstract public function getConsents($userId);
+
+
+	/**
+	 * Parse consent storage configuration.
+	 *
+	 * This function parses the configuration for a consent storage method. An exception
+	 * will be thrown if configuration parsing fails.
+	 *
+	 * @param mixed $config  The configuration.
+	 * @return sspmod_consent_Store  An object which implements of the sspmod_consent_Store class.
+	 */
+	public static function parseStoreConfig($config) {
+
+		if (is_string($config)) {
+			$config = array($config);
+		}
+
+		if (!is_array($config)) {
+			throw new Exception('Invalid configuration for consent store option: ' .
+				var_export($config, TRUE));
+		}
+
+		if (!array_key_exists(0, $config)) {
+			throw new Exception('Consent store without name given.');
+		}
+
+		$className = SimpleSAML_Module::resolveClass($config[0], 'Consent_Store',
+			'sspmod_consent_Store');
+
+		unset($config[0]);
+		return new $className($config);
+	}
+
+}
+
+?>
\ No newline at end of file
diff --git a/modules/consent/templates/default/consentform.php b/modules/consent/templates/default/consentform.php
new file mode 100644
index 0000000000000000000000000000000000000000..6931dfd4353f804d79530f51c045be2f79174399
--- /dev/null
+++ b/modules/consent/templates/default/consentform.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * Template form for giving consent.
+ *
+ * Parameters:
+ * - 'srcMetadata': Metadata/configuration for the source.
+ * - 'dstMetadata': Metadata/configuration for the destination.
+ * - 'yesTarget': Target URL for the yes-button. This URL will receive a POST request.
+ * - 'yesData': Parameters which should be included in the yes-request.
+ * - 'noTarget': Target URL for the no-button. This URL will receive a GET request.
+ * - 'noData': Parameters which should be included in the no-request.
+ * - 'attributes': The attributes which are about to be released.
+ * - 'sppp': URL to the privacy policy of the destination, or FALSE.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+assert('is_array($this->data["srcMetadata"])');
+assert('is_array($this->data["dstMetadata"])');
+assert('is_string($this->data["yesTarget"])');
+assert('is_array($this->data["yesData"])');
+assert('is_string($this->data["noTarget"])');
+assert('is_array($this->data["noData"])');
+assert('is_array($this->data["attributes"])');
+assert('$this->data["sppp"] === FALSE || is_string($this->data["spp"])');
+
+
+/* Parse parameters. */
+
+if (array_key_exists('name', $this->data['srcMetadata'])) {
+	$srcName = $this->data['srcMetadata']['name'];
+	if (is_array($srcName)) {
+		$srcName = $this->t($srcName);
+	}
+} else {
+	$srcName = $this->data['srcMetadata']['entityid'];
+}
+
+if (array_key_exists('name', $this->data['dstMetadata'])) {
+	$dstName = $this->data['dstMetadata']['name'];
+	if (is_array($dstName)) {
+		$dstName = $this->t($dstName);
+	}
+} else {
+	$dstName = $this->data['dstMetadata']['entityid'];
+}
+
+$attributes = $this->data['attributes'];
+
+
+$this->data['header'] = 'Consent'; /* TODO: translation */
+$this->includeAtTemplateBase('includes/header.php');
+?>
+<div id="content">
+
+<p>
+<?php echo $this->t('{consent:consent_notice}'); ?> <strong><?php echo htmlspecialchars($dstName); ?></strong>.
+<?php echo $this->t('{consent:consent_accept}', array('IDPNAME' => htmlspecialchars($srcName))) ?>
+</p>
+
+<?php
+if ($this->data['sppp'] !== FALSE) {
+	echo "<p>" . htmlspecialchars($this->t('consent_privacypolicy')) . " ";
+	echo "<a target='_new_window' href='" . htmlspecialchars($this->data['sppp']) . "'>" . htmlspecialchars($dstName) . "</a>";
+	echo "</p>";
+}
+?>
+
+<form style="display: inline" action="<?php echo htmlspecialchars($this->data['yesTarget']); ?>">
+	<input type="submit" name="yes" id="yesbutton" value="<?php echo $this->t('{consent:yes}') ?>" />
+<?php
+foreach ($this->data['yesData'] as $name => $value) {
+	echo('<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value) . '" />');
+}
+if ($this->data['usestorage']) {
+	echo('<input type="checkbox" name="saveconsent" value="1" /> ' . $this->t('{consent:remember}'));
+}
+?>
+</form>
+
+<form style="display: inline; margin-left: .5em;" action="<?php echo htmlspecialchars($this->data['noTarget']); ?>" method="GET">
+<?php
+foreach ($this->data['noData'] as $name => $value) {
+	echo('<input type="hidden" name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($value) . '" />');
+}
+?>
+	<input type="submit" id="nobutton" value="<?php echo htmlspecialchars($this->t('{consent:no}')) ?>" />
+</form>
+
+<p>
+<table style="font-size: x-small">
+<?php
+foreach ($attributes as $name => $value) {
+	$nameTag = '{attributes:attribute_' . strtolower($name) . '}';
+	if ($this->getTag($nameTag) !== NULL) {
+		$name = $this->t($nameTag);
+	}
+
+	if (sizeof($value) > 1) {
+		echo '<tr><td>' . htmlspecialchars($name) . '</td><td><ul>';
+		foreach ($value AS $v) {
+			echo '<li>' . htmlspecialchars($v) . '</li>';
+		}
+		echo '</ul></td></tr>';
+	} else {
+		echo '<tr><td>' . htmlspecialchars($name) . '</td><td>' . htmlspecialchars($value[0]) . '</td></tr>';
+	}
+}
+
+?>
+</table>
+</p>
+
+<?php
+$this->includeAtTemplateBase('includes/footer.php');
+?>
\ No newline at end of file
diff --git a/modules/consent/templates/default/noconsent.php b/modules/consent/templates/default/noconsent.php
new file mode 100644
index 0000000000000000000000000000000000000000..b865cbef1525ca1195fdbe69a33079bd0cf1e01b
--- /dev/null
+++ b/modules/consent/templates/default/noconsent.php
@@ -0,0 +1,21 @@
+<?php
+	$this->data['header'] = $this->t('{consent:noconsent_title}');;
+	$this->data['icon'] = 'bomb_l.png';
+	$this->includeAtTemplateBase('includes/header.php');
+?>
+
+
+<div id="content">
+
+	<h2><?php echo($this->data['header']); ?></h2>
+	<p><?php echo($this->t('{consent:noconsent_text}')); ?></p>
+
+<?php
+	if($this->data['resumeFrom']) {
+		echo('<p><a href="' . htmlspecialchars($this->data['resumeFrom']) . '">');
+		echo($this->t('{consent:noconsent_return}'));
+		echo('</a></p>');
+	}
+?>
+
+<?php $this->includeAtTemplateBase('includes/footer.php'); ?>
\ No newline at end of file
diff --git a/modules/consent/www/getconsent.php b/modules/consent/www/getconsent.php
new file mode 100644
index 0000000000000000000000000000000000000000..284f904fcfe192d7413f2e4b8c89c5add2df2446
--- /dev/null
+++ b/modules/consent/www/getconsent.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * This script displays a page to the user, which requests that the user
+ * authorizes the release of attributes.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+if (!array_key_exists('StateId', $_REQUEST)) {
+	throw new SimpleSAML_Error_BadRequest('Missing required StateId query parameter.');
+}
+
+$id = $_REQUEST['StateId'];
+$state = SimpleSAML_Auth_State::loadState($id, 'consent:request');
+
+
+if (array_key_exists('yes', $_REQUEST)) {
+	/* The user has pressed the yes-button. */
+
+	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'];
+		$destination = $state['consent:store.destination'];
+		$attributeSet = $state['consent:store.attributeSet'];
+		$store->saveConsent($userId, $destination, $attributeSet);
+	}
+
+	SimpleSAML_Auth_ProcessingChain::resumeProcessing($state);
+}
+
+
+/* Show consent form. */
+
+$globalConfig = SimpleSAML_Configuration::getInstance();
+$t = new SimpleSAML_XHTML_Template($globalConfig, 'consent:consentform.php');
+$t->data['srcMetadata'] = $state['Source'];
+$t->data['dstMetadata'] = $state['Destination'];
+$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'] = $state['Attributes'];
+
+if (array_key_exists('privacypolicy', $state['Destination'])) {
+	$privacypolicy = $state['Destination']['privacypolicy'];
+} elseif (array_key_exists('privacypolicy', $state['Source'])) {
+	$privacypolicy = $state['Source']['privacypolicy'];
+} else {
+	$privacypolicy = FALSE;
+}
+if($privacypolicy !== FALSE) {
+	$privacypolicy = str_replace('%SPENTITYID%', urlencode($spentityid),
+		$privacypolicy);
+}
+$t->data['sppp'] = $privacypolicy;
+
+switch ($state['consent:focus']) {
+case NULL:
+	break;
+case 'yes':
+	$t->data['autofocus'] = 'yesbutton';
+	break;
+case 'no':
+	$t->data['autofocus'] = 'nobutton';
+	break;
+}
+
+if (array_key_exists('consent:store', $state)) {
+	$t->data['usestorage'] = TRUE;
+} else {
+	$t->data['usestorage'] = FALSE;
+}
+
+$t->show();
+exit;
+
+?>
\ No newline at end of file
diff --git a/modules/consent/www/noconsent.php b/modules/consent/www/noconsent.php
new file mode 100644
index 0000000000000000000000000000000000000000..c84e5060693e5519ed2b7cd9f4ec8c562e84fa6e
--- /dev/null
+++ b/modules/consent/www/noconsent.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * This is the page the user lands on when choosing "no" in the consent form.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+
+if (!array_key_exists('StateId', $_REQUEST)) {
+	throw new SimpleSAML_Error_BadRequest('Missing required StateId query parameter.');
+}
+
+$id = $_REQUEST['StateId'];
+$state = SimpleSAML_Auth_State::loadState($id, 'consent:request');
+
+$resumeFrom = SimpleSAML_Module::getModuleURL('consent/getconsent.php');
+$resumeFrom = SimpleSAML_Utilities::addURLParameter($resumeFrom, array('StateId' => $id));
+
+$globalConfig = SimpleSAML_Configuration::getInstance();
+
+$t = new SimpleSAML_XHTML_Template($globalConfig, 'consent:noconsent.php');
+$t->data['dstMetadata'] = $state['Destination'];
+$t->data['resumeFrom'] = $resumeFrom;
+$t->show();
+
+?>
\ No newline at end of file